#!/bin/bash
#==============================================================#
# 脚本名     :   DMShellInstall
# 创建时间   :   2022-11-03 10:02:09
# 更新时间   :   2024-04-26 19:54:18
# 描述      :   达梦数据库一键安装脚本（单机/数据守护/DSC）
# 路径      :   /soft/DMShellInstall
# 版本      :   1.0.0
# 作者      :   Lucifer(pc1107750981@163.com) yuanzijian(yzj@dameng.com) xiefenfen(xff@dameng.com)
# Copyright (C) 2021-2023 Pengcheng Liu       Zijian Yuan                Fenfen Xie
#==============================================================#
#==============================================================#
#                         全局变量                              #
#==============================================================#
# 数据库的节点号，默认为 1
node_num=1
# 安装脚本以及软件包存放位置
software_dir=$(dirname "$(readlink -f "$0")")
# 删除脚本生成的LOG日志文件
[[ $(find "${software_dir}" -name "print_dm_install_*.log" 2>/dev/null) ]] && rm -f "${software_dir}"/print_dm_install_*.log
# 达梦数据库安装日志
dminstalllog="${software_dir}"/print_dm_install_$(date +"20%y%m%d%H%M%S").log
# 系统当前时间
current=$(date +%Y%m%d%H%M%S)
# 定义服务器标记
os_flag=OS1
#服务器类型
os_type=rhel
# 操作系统版本
os_version=7
# 服务器物理内存大小
os_memory=$(free -k | awk '/Mem:/ {print $2+0}')
#服务器磁盘列表
declare -a disk_list_arr
all_disk=$(lsblk -l | awk '/^sd|vd/{print $1}' | sed -n '/[0-9]$/!p')
# 主机名
hostname=''
# 存储生成的随机字符串
random_string=''
# 主机用户dmdba密码，默认 Dameng@123
dm_passwd=Dameng@123
# 达梦数据库名称，默认 DAMENG
db_name=DAMENG
# 实例名，默认 DMSERVER
instance_name=DMSERVER
# 数据文件使用的簇大小(32)，可选值：16, 32, 64，单位：页
extent_size=32
# 数据页大小(32)，可选值：4, 8, 16, 32，单位：K
page_size=32
# 大小写敏感(Y)，可选值：Y/N，1/0
typeset -u case_sensitive
case_sensitive=Y
# 字符集(1)，可选值：0[GB18030]，1[UTF-8]，2[EUC-KR]
charset=1
#0：不兼容，1：兼容SQL92标准，2：部分兼容ORACLE，3：部分兼容MS SQL SERVER，4：部分兼容MYSQL，5：兼容DM6，6：部分兼容TERADATA，7：部分兼容POSTGRES
compatible_mode=0
#设置空格填充模式(0)，可选值：0/1
blank_pad_mode=0
# 日志文件大小(1024)，单位为：M，范围为：256M ~ 2G
log_size=1024
# 是否开启归档模式，默认开启
typeset -u enable_arch
enable_arch=Y
# 归档空间大小，单位M，默认值 102400M
space_limit=102400
# 监听端口号(5236)
port_num=5236
# 达梦数据库SYSDBA用户密码，默认 SYSDBA
sysdba_pwd=SYSDBA
# 达梦软件安装目录，默认 /dm
env_dm_home=/dm
# 数据库存放目录，默认 /dmdata
data_dir=/dmdata
# 数据库备份目录，默认 /dmbak
backup_dir=/dmbak
# 数据库归档目录，默认 /dmarch
arch_dir=/dmarch
# 数据库core文件目录，默认 /dmbak
core_dir=$backup_dir/dmcore
# 数据库用户脚本目录，默认 /home/dmdba/scripts
scripts_dir=/home/dmdba/scripts
# 数据库备份模式，1是全备，2是增量，默认 2
backup_mode=2
# 时间服务器IP
timeserver_ip=''
# DSC第三方确认IP
link_check_ip=''
# 仅配置操作系统，不安装达梦软件以及数据库，默认为 N
typeset -u only_conf_os
only_conf_os=N
# 仅安装达梦数据库软件，不创建数据库，默认为 N
typeset -u install_until_db
install_until_db=N
# 仅初始化数据库，默认为 N
typeset -u only_init_db
only_init_db=N
# 建好数据库后，优化数据库，默认为 Y
typeset -u optimize_db
optimize_db=Y
# 脚本仅优化数据库，默认为 N
typeset -u only_optimize_db
only_optimize_db=N
# 优化数据库时，内存占比百分数，默认为 80
mem_percent=80
#并发量较高的OLTP类型系统此参数设置为1，并发量不高的一般业务系统和OLAP类的系统此参数设置为0,影响SORT_FLAG和UNDO_RETENTION
oltp_mode=0
# 达梦安装镜像名称，默认为空
dm_iso_name=''
# 达梦密钥路径
key_path=''
# 安装模式
typeset -l dm_install_mode
# 添加一个参数校验是否合格的标志，函数中只会修改为0，不会再修改为1；
ARG_CHECK_FLAG=1
#==============================================================#
#                    数据守护 && DSC 部署全局变量              #
#==============================================================#
# 实时/即时主备业务IP
wc_public_ip=
# 异步主备业务IP
as_public_ip=
# 数据守护心跳IP
wc_mal_ip=
# 主机名，心跳IP，业务IP 数组定义
declare -a wc_hostname_arr
declare -a wc_hostip_arr
declare -a wc_pubip_arr
declare -a as_pubip_arr
declare -a wc_malip_arr
# 定义标签值，默认为0
os_flag_value=0
# 操作系统 ISO 是否需要挂载，默认为 Y
typeset -u iso
iso=Y
# 定义ssh端口
os_ssh_port=22
#==============================================================#
#                    数据守护全局变量                          #
#==============================================================#
# 守护进程组名
dw_group_name=GRP1
# MAL 监听端口，默认为 5336
mal_port_num=5336
# 守护进程端口，默认为 5436
mal_dw_port_num=5436
# 实例监听守护进程端口，默认为 5536
mal_inst_dw_port_num=5536
# 数据守护切换模式，默认为手动
typeset -u dw_mode
dw_mode=MANUAL
# 数据守护性能模式
arch_wait_apply=1
# 数据守护归档模式
arch_type=0
typeset -u dw_arch_type
dw_arch_type=REALTIME
#异步归档时间间隔
repeat_interval=60
#==============================================================#
#                    DSC 全局常量                              #
#==============================================================#
# udev规则文件路径
UDEV_RULES_PATH="/etc/udev/rules.d"
# ASM设备组路径
ASM_DEVICE_PATH="/dev/asmdisk"
# udev权限规则文件名
DM_PERMISSIONS_RULES="12-dm-permissions.rules"
# udev ASM设备规则文件名
DM_ASMDEVICES_RULES="99-dm-asmdevices.rules"
# multipath配置文件路径
MULTIPATH_CONF_PATH="/etc/multipath.conf"
#==============================================================#
#                    DSC 全局变量                              #
#==============================================================#
#定义DSC集群db_name
typeset -u dsc_db_name
dsc_db_name=DSC
#定义节点实例名前缀，建议和DB_NAME保持一致
typeset -l dsc_ep_name
dsc_ep_name=DSC
#是否格式化共享存储盘
typeset -u format_disk
format_disk=Y
#需要过滤的磁盘
typeset -l fil_disk
#定义dcr磁盘
typeset -l dcr_disk
dcr_disk=
#定义vote磁盘
typeset -l vote_disk
vote_disk=
#定义redo日志磁盘
typeset -l log_disk
log_disk=
#定义归档磁盘
typeset -l arch_disk
arch_disk=
#定义数据磁盘
typeset -l data_disk
data_disk=
#定义磁盘数组
declare -a disk_arr
#定义asm磁盘数组
declare -a asm_disk_arr
#定义ASM实例密码
asm_pwd=Dameng1
#定义css_dcr_ep_port端口
css_dcr_ep_port=9341
#定义asm_dcr_ep_port端口
asm_dcr_ep_port=9351
#定义db_dcr_ep_port端口
db_dcr_ep_port=9361
#定义asvrmal_mal_port端口
asvrmal_mal_port=9451
#定义db_mal_port端口
db_mal_port=9461
#定义多路径参数
typeset -u multipath
multipath=N
#是否配置本地归档
typeset -u is_local_arch
is_local_arch=N
#数据分配单元
au_size=4
#DMDASM实例日志大小
redo_size=128
#redo日志的冗余模式
redo_redun=1
#归档盘的冗余模式
arch_redun=1
#数据盘的冗余模式
data_redun=1
#SYSTEM/MAIN/ROLL 表空间数据文件副本数
data_mirror=1
#联机日志文件副本数
log_mirror=1
#数据文件条带化粒度
data_striping=32
#联机日志条带化粒度
log_striping=64
#dm.ctl 和 dm_service.prikey 文件副本数
ctl_mirror=1
#归档文件配置在 ASM 上镜像
arch_asm_mirror=1
#归档文件配置在 ASM 上条带化类型
arch_asm_striping=64
#==============================================================#
#                    Art Print                                 #
#==============================================================#
function script_print() {
  echo
  cat <<-'EOF'
 ███████   ████     ████  ████████ ██               ██  ██ ██                    ██              ██  ██
░██░░░░██ ░██░██   ██░██ ██░░░░░░ ░██              ░██ ░██░██                   ░██             ░██ ░██
░██    ░██░██░░██ ██ ░██░██       ░██       █████  ░██ ░██░██ ███████   ██████ ██████  ██████   ░██ ░██
░██    ░██░██ ░░███  ░██░█████████░██████  ██░░░██ ░██ ░██░██░░██░░░██ ██░░░░ ░░░██░  ░░░░░░██  ░██ ░██
░██    ░██░██  ░░█   ░██░░░░░░░░██░██░░░██░███████ ░██ ░██░██ ░██  ░██░░█████   ░██    ███████  ░██ ░██
░██    ██ ░██   ░    ░██       ░██░██  ░██░██░░░░  ░██ ░██░██ ░██  ░██ ░░░░░██  ░██   ██░░░░██  ░██ ░██
░███████  ░██        ░██ ████████ ░██  ░██░░██████ ███ ███░██ ███  ░██ ██████   ░░██ ░░████████ ███ ███
░░░░░░░   ░░         ░░ ░░░░░░░░  ░░   ░░  ░░░░░░ ░░░ ░░░ ░░ ░░░   ░░ ░░░░░░     ░░   ░░░░░░░░ ░░░ ░░░
EOF
}
#==============================================================#
#                    UPPERCASE                                 #
#==============================================================#
function upper() {
  echo "${1^^}"
}
#==============================================================#
#                    LOWERCASE                                 #
#==============================================================#
function lower() {
  echo "${1,,}"
}
#==============================================================#
#                    获取随机数                                #
#==============================================================#
function rand() {
  min=$1
  max=$(($2 - min + 1))
  num=$((RANDOM + 1000000000)) #增加一个10位的数再求余
  echo $((num % max + min))
}
#==============================================================#
#                    获取长度为5的字符串                       #
#==============================================================#
generate_random_string() {
  local length=5                           # 指定生成的随机字符串长度为五位
  local chars='abcdefghijklmnopqrstuvwxyz' # 可选的字符集合，这里是小写字母
  # 使用循环生成指定长度的字符串
  for i in $(
    seq 1 $length
  ); do
    # shellcheck disable=SC2004
    local random_index=$(($RANDOM % ${#chars})) # 生成一个随机索引
    random_string+="${chars:$random_index:1}"   # 将随机索引对应的字符添加到字符串中
  done
}
#==============================================================#
#                    判断文件是否存在                          #
#==============================================================#
function check_file() {
  if [[ -e "$1" ]]; then
    return 0
  else
    return 1 # 文件不存在
  fi
}
#==============================================================#
#                    安装rpm包                                 #
#==============================================================#
# 安装软件包（如果未安装）
function install_packages_if_not_present() {
  for package in "$@"; do
    if ! rpm -q "$package" >/dev/null; then
      yum install -y -q "$package" --skip-broken >/dev/null 2>&1
    fi
  done
}
#==============================================================#
#                    远程执行命令                              #
#==============================================================#
function exec_ssh_cmd {
  local ssh_cmd="ssh -q -p $os_ssh_port -t"
  local node="$1"
  local cmd="$2"
  # ssh命令
  $ssh_cmd "${node}" "${cmd}"
}
#==============================================================#
#                    发送文件到目标服务器                      #
#==============================================================#
function exec_scp_cmd {
  local scp_cmd="scp -p -q -P $os_ssh_port -r"
  local file_name="$1"
  local node="$2"
  local path="$3"
  local is_s_d="$4"
  # scp命令
  case "$is_s_d" in
  "s") $scp_cmd "${node}:${path}" "${file_name}" ;;
  "d") $scp_cmd "${file_name}" "${node}:${path}" ;;
  esac
}
#==============================================================#
#                    执行SQL命令                               #
#==============================================================#
function execute_sql() {
  local sql_command="$1"
  echo -e "$sql_command\n"
  su - dmdba -c "disql -S -L SYSDBA/SYSDBA:$port_num -c \"set heading off\" -e \"$sql_command\""
  return $?
}
#==============================================================#
#                    执行SQL脚本                               #
#==============================================================#
function execute_script() {
  local script_name="$1"
  local script_path="${scripts_dir}/${script_name}"
  su - dmdba -c "disql -S SYSDBA/SYSDBA:$port_num '\`$script_path'"
  return $?
}
#==============================================================#
#                    日志输出颜色                              #
#==============================================================#
function color_printf() {
  declare -A color_map=(
    ["red"]='\E[31m'
    ["green"]='\E[1;32m'
    ["yellow"]='\E[33m'
    ["blue"]='\E[34m'
    ["white"]='\E[37m'
  )
  local res='\E[0m'
  local color=${color_map[$1]:-"$2"}
  printf "${color}%-20s %-30s %-50s\n${res}" "$2" "$3" "$4"
}
#==============================================================#
#                    打印日志                                  #
#==============================================================#
function log_print() {
  echo
  color_printf green "#==============================================================#"
  color_printf green "$1"
  color_printf green "#==============================================================#"
  [[ -n $2 ]] && echo -e "\n$2"
  echo
}
#==============================================================#
#                    备份系统文件                              #
#==============================================================#
# 定义名为 bak_file 的函数，用于备份指定文件
function bak_file() {
  # 获取第一个传入参数作为要备份的文件路径
  local file_path=$1
  # 如果文件存在则备份
  if check_file "$file_path"; then
    # 检查文件中是否包含 "# DMBegin" 字符串
    if [[ $(grep -E -c "# DMBegin" "$file_path") = 0 ]]; then
      # 如果不包含，则使用 nohup 命令备份该文件，并附加 .original 后缀
      # 这里使用了 \cp 命令，而不是 cp 命令，以避免出现别名或其他问题
      nohup \cp -prv "$file_path"{,.original} >/dev/null 2>&1
    else
      # 如果文件中包含 "# DMBegin" 字符串，则先备份当前文件，再恢复原始文件
      # 备份当前文件，将其保存为原来的名称加上当前时间戳的形式
      nohup \cp -prv "$file_path"{,."$current"} >/dev/null 2>&1
      # 恢复原始文件，将其替换回原始的备份文件
      nohup \cp -prv "$file_path"{.original,} >/dev/null 2>&1
    fi
  else
    touch "$file_path".original
  fi
}
#==============================================================#
#                    获取操作系统类型和版本                    #
#==============================================================#
# 定义操作系统类型和版本
function define_os_flag() {
  local or_file=/etc/os-release
  local sr_file=/etc/system-release
  local rr_file=/etc/redhat-release
  # 获取OS类型
  if check_file "${or_file}"; then
    os_type=$(awk <"${or_file}" -F= '/^ID=/ { gsub("\"", "", $2); print $2 }')
  else
    os_type=$(grep <"$([ -f ${sr_file} ] && echo ${sr_file} || echo ${rr_file})" -oP '^[A-Za-z]+')
  fi
  # 获取OS版本
  os_version=$(grep <"$([ -f "${sr_file}" ] && echo ${sr_file} || echo ${rr_file})" -oP '(?<=release\s|V)[[:digit:]]+')
  # 定义OS标记
  case $os_type in
  # 红帽系列系统，版本是8，9的系统，定义os_flag=OS2
  Red | CentOS | rhel | centos | ol | rocky | anolis)
    if [[ $os_version -eq 8 || $os_version -eq 9 ]]; then
      os_flag=OS2
    fi
    ;;
  uos)
    # 判断UOS系统是1060a版本还是1060e版本
    [[ "$(grep </etc/issue -oE "1060a|1060e")" == "1060a" ]] && os_flag="OS2" || os_flag="OS3"
    ;;
  openEuler)
    os_flag=OS4
    ;;
  *)
    # 如果系统是 centos6 | centos7 | fedora | kylin | neokylin 等其他系统定义os_flag=OS1
    os_flag=OS1
    ;;
  esac
  #获取主机上所有IP地址，按空格分割IP地址，并逐一比较是否和传递的列表中第一个IP地址匹配
  IFS=' ' read -ra current_ips <<<"$(hostname -I)"
  # 遍历当前 IP 地址数组，并检查是否包含在过滤后的数组中
  for current_ip in "${current_ips[@]}"; do
    if [[ "${wc_pubip_arr[*]}" =~ $current_ip ]]; then
      os_flag_value=1 # 如果当前 IP 包含在过滤后的数组中，更新标签值为 1
    fi
  done
}
#==============================================================#
#                    去除显示登录提示和密码复杂度              #
#==============================================================#
function rm_passwd_complexity() {
  # 除去显示 last login 信息
  sed -i 's/^session\+[[:space:]]\+include[[:space:]]\+postlogin/#&/g' /etc/pam.d/su
  # 判断文件是否存在
  local or_file=/etc/os-release
  if check_file "${or_file}"; then
    os_type=$(awk <"${or_file}" -F= '/^ID=/ { gsub("\"", "", $2); print $2 }')
  fi
  # 针对kylin|openEuler系统去除密码复杂度
  case "$os_type" in
  uos)
    sed -i 's/^password    requisite     pam_deepin_pw_check.so try_first_pass local_users_only retry=1 enforce_for_root authtok_type=/#&/' /etc/pam.d/system-auth
    sed -i 's/use_authtok$//' /etc/pam.d/system-auth
    ;;
  kylin | openEuler)
    sed -i 's/^password\+[[:space:]]\+requisite[[:space:]]\+pam_pwquality.so/#&/g' /etc/pam.d/system-auth
    sed -i 's/use_authtok$//' /etc/pam.d/system-auth
    ;;
  esac
}
#==============================================================#
#                    操作系统命令检验                          #
#==============================================================#
function command_check() {
  # 校验系统命令，如果命令不存在为真，标记不存在
  check_command_existence() {
    local command_name=$1
    # 如果命令不存在，则输出错误信息
    if ! command -v "$command_name" >/dev/null 2>&1; then
      ARG_CHECK_FLAG=0
      echo
      color_printf red "Error: $command_name 命令未找到！"
      echo
    fi
  }
  # 定义命令参数
  declare -a commands=("dirname" "mount" "tar")
  for shell_c in "${commands[@]}"; do
    check_command_existence "$shell_c"
  done
}
#==============================================================#
#                    密码强度检验                              #
#==============================================================#
function passwd_check() {
  # 定义传进来的密码
  local password="$2"
  # 定义规则关联数组
  declare -A rules=(
    ["A"]="^.{9,}$"
    ["A_DESC"]="密码长度至少为9位"
    ["B"]="[a-z]"
    ["B_DESC"]="密码中必须包含至少一个小写字母"
    ["C"]="[A-Z]"
    ["C_DESC"]="密码中必须包含至少一个大写字母"
    ["D"]="[0-9]"
    ["D_DESC"]="密码中必须包含至少一个数字"
    ["E"]="[^[:alnum:]]"
    ["E_DESC"]="密码中必须包含至少一个特殊字符"
  )
  # 遍历关联数组，检查每个规则是否符合
  for rule in "${!rules[@]}"; do
    # 判断当前规则是匹配规则还是描述规则
    if [[ $rule =~ _DESC$ ]]; then
      continue # 跳过描述规则
    fi
    # 获取当前规则的描述
    desc="${rules[${rule}_DESC]}"
    # 检查密码是否符合当前规则
    if ! [[ $password =~ ${rules[$rule]} ]]; then
      ARG_CHECK_FLAG=0
      echo
      color_printf red " 请检查参数[ $1 ] ,密码不符合要求（长度需要9位以上，包含大小写字母，数字，特殊符号）：$desc"
      echo
    fi
  done
}
#==============================================================#
#                    检查 IP 是否合法                          #
#==============================================================#
function check_ip() {
  local ip="$1"
  local msg="$2"
  local pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
  # 检查 IP 是否合法
  if [[ ! $ip =~ $pattern ]]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $msg ] 的 IP 地址 $ip 是否合法！"
    echo
  fi
  # 检测 IP地址是否可以ping通
  if ! ping -c 2 -w 5 "$ip" >/dev/null 2>&1; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $msg ] 的 IP 地址 $ip 是否正确！"
  fi
}
#==============================================================#
#                    检查 IP 是否是第一节点IP                  #
#==============================================================#
function check_ip_arr() {
  local msg="$1"
  local -a ips=("${@:2}") # 远程主机 IP 地址数组
  echo -e "\033[37m校验 ${ips[*]} 地址，请等待！！！"
  # 获取主机上所有IP地址，按空格分割IP地址，并逐一比较是否和传递的列表中第一个IP地址匹配
  IFS=' ' read -ra current_ips <<<"$(hostname -I)"
  local match_found=0
  for ip in "${current_ips[@]}"; do
    if [ "$ip" == "${ips[0]}" ]; then
      match_found=1
      break
    fi
  done
  # 如果没有找到匹配的IP地址，则退出脚本
  if [ "$match_found" -eq 0 ]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $msg ]，输入的第一个IP：${ips[0]} 不是当前机器IP，请重新输入！"
    echo
  fi
  # 判断IP列表中有没有重复的IP
  # 去重并存储到新数组
  # shellcheck disable=SC2046
  if [ ${#ips[@]} -ne $(printf '%s\n' "${ips[@]}" | sort -u | wc -l) ]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "有重复的IP地址列表，退出脚本!"
    echo
  fi
  # 校验ip地址是否合法正确
  wc_pubip_matched=$(printf "%s\n" "${wc_pubip_arr[@]}")
  as_pubip_matched=$(printf "%s\n" "${as_pubip_arr[@]}")
  for ip in "${ips[@]:1}"; do
    case $msg in
    "-dpi")
      if [[ $wc_pubip_matched =~ $ip ]]; then
        check_ip "$ip" "-dpi"
      elif [[ $as_pubip_matched =~ $ip ]]; then
        check_ip "$ip" "-api"
      else
        check_ip "$ip" "-dmoi"
      fi
      ;;
    *)
      check_ip "$ip" "-dmi"
      ;;
    esac
  done
}
#==============================================================#
#                    校验 IP 和 共享磁盘                       #
#==============================================================#
function validate_ip_disk() {
  # 校验密码参数,ip参数和磁盘参数
  case "$dm_install_mode" in
  "datawatch")
    # 把公共IP加到数组里面
    [[ -n "$wc_public_ip" ]] && wc_hostip_arr+=("${wc_pubip_arr[@]}")
    [[ -n "$as_public_ip" ]] && wc_hostip_arr+=("${as_pubip_arr[@]}")
    [[ -n "$dw_monitor_ip" ]] && wc_hostip_arr+=("$dw_monitor_ip")
    ;;
  *)
    [[ -n "$wc_public_ip" ]] && wc_hostip_arr+=("${wc_pubip_arr[@]}")
    ;;
  esac
  if [[ $node_num -eq 1 ]]; then
    log_print "校验IP地址和磁盘" ""
    ## 校验服务器IP
    check_ip_arr "-dpi" "${wc_hostip_arr[@]}"
    echo
    check_ip_arr "-dmi" "${wc_malip_arr[@]}"
    if [[ $dm_install_mode = "dsc" || $dm_install_mode = "dscm" ]]; then
      #校验共享磁盘，判断传进去的磁盘是否有重复的uuiid值，有的话脚本直接退出，只在一节点执行
      for disk in "$dcr_disk" "$vote_disk" "$arch_disk" "$log_disk" "$data_disk"; do
        if [ -n "$disk" ]; then
          case "$disk" in
          "$dcr_disk")
            check_disk -dcd "$disk"
            ;;
          "$vote_disk")
            check_disk -vod "$disk"
            ;;
          "$arch_disk")
            check_disk -ard "$disk"
            ;;
          "$log_disk")
            check_disk -lod "$disk"
            ;;
          "$data_disk")
            check_disk -dad "$disk"
            ;;
          esac
        fi
      done
    fi
  fi
  # 所有参数校验完成，根据ARG_CHECK_FLAG标志，判断是否推出本次安装
  if [ $ARG_CHECK_FLAG -eq 0 ]; then
    echo
    color_printf red "参数校验不通过！请检查屏幕上输出的参数异常信息或者查看${dminstalllog} 文件获取异常参数信息，处理后再次校验！"
    echo
    exit 1
  fi
  #fi
  if [[ $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    # 把传进的磁盘加到数组里面
    # shellcheck disable=SC2207
    disk_arr=($([[ $dm_install_mode == "dsc" ]] && echo "$dcr_disk $vote_disk" || echo "$dcr_disk") ${log_disk:+$log_disk} ${arch_disk:+$arch_disk} "$data_disk")
    # shellcheck disable=SC2207
    # 定义创建ASM磁盘数组函数
    asm_disk_arr=($([[ $dm_install_mode == "dsc" ]] && echo "dmdcr dmvote" || echo "dmdcrv ") ${log_disk:+dmlog} ${arch_disk:+dmarch} dmdata)
  fi
}
#==============================================================#
#                    Get UUID                                  #
#==============================================================#
#获取磁盘的UUID
function get_uuid() {
  local uuid
  case $os_version in
  6)
    uuid=$(/sbin/scsi_id -g -u -d "$1")
    ;;
  *)
    if [[ $1 =~ [0-9] ]]; then
      uuid=$(udevadm info --query=all --name="$1" | awk -F'=' '/ID_PART_ENTRY_UUID/ {print $2}')
    else
      uuid=$(/usr/lib/udev/scsi_id -g -u -d "$1")
    fi
    ;;
  esac
  printf '%s' "$uuid"
}
function get_dm_uuid() {
  dmsetup info -c | grep "$1" | awk '{print $8}'
}
#==============================================================#
#                    获取磁盘容量                               #
#==============================================================#
function disk_storage() {
  lsblk -b -o SIZE -n "${1}" | awk '$1 >= 1024*1024*1024 {printf "%.1fG\n", $1/1024/1024/1024}'
}
#==============================================================#
#                    过滤唯一uuid磁盘                           #
#==============================================================#
function filter_disk() {
  # 以逗号分割数组
  IFS=',' read -ra fil_disk_arr <<<"$fil_disk"
  declare -a disk_list=()
  # 使用for-in循环枚举all_disk数组中所有元素
  for ad in ${all_disk// / }; do
    # 判断$ad是否不存在于$fd中，如果是则执行下列操作
    if ! [[ ${fil_disk_arr[*]} =~ $ad ]]; then
      # 将"/dev/$disk"添加到disk_list中
      disk_list+=("/dev/$ad")
    fi
  done
  declare -A uuids=() # 声明存储磁盘 UUID 的关联数组
  declare -A sizes=() # 声明存储磁盘容量大小的关联数组
  # 处理每个磁盘的循环
  for disk in "${disk_list[@]}"; do
    sizes[$disk]=$(disk_storage "$disk") # 保存磁盘容量大小到关联数组中
    uuid=$(get_uuid "$disk")             # 获取磁盘的 UUID
    # 如果磁盘有 UUID 并且它还没有添加到数组中，就将其添加到数组中
    if [[ $uuid != "" && ! ${uuids[*]} =~ $uuid ]]; then
      uuids[$disk]=$uuid
    fi
  done
  echo
  # 遍历存储磁盘 UUID 的关联数组并打印每个磁盘的 UUID、名称和容量大小
  for disk in "${!uuids[@]}"; do
    echo "${uuids[$disk]} $disk ${sizes[$disk]}"
  done | sort -k3n -k2
  echo
}
#==============================================================#
#                    检查 磁盘是否具有唯一uuid                   #
#==============================================================#
function check_disk() {
  local declar_opt="$1"  # 第一个参数，指定要检查的磁盘选项
  local disk_list_tmp=() # 用于存储待检查的磁盘列表
  echo
  color_printf white "校验 $2 磁盘，请等待！！！"
  # 处理磁盘列表参数
  if [[ $2 =~ "," ]]; then
    IFS=',' read -ra disk_list <<<"$2" # 将以逗号分割的字符串转换为数组
    for disk in "${disk_list[@]}"; do  # 循环遍历磁盘列表
      disk_list_arr+=("$disk")         # 将不是选项参数的磁盘加入待检查列表
    done
  else
    for disk in "${@:2}"; do   # 从第三个参数开始循环遍历磁盘列表
      disk_list_arr+=("$disk") # 将不是选项参数的磁盘加入待检查列表
    done
  fi
  # 遍历待检查的磁盘列表
  for disk in "${disk_list_arr[@]}"; do
    local uuid=""
    if [[ $disk =~ "dev" ]]; then
      uuid=$(get_uuid "$disk") # 获取磁盘的UUID
    else
      uuid=$(get_dm_uuid "$disk") # 获取设备映射的UUID
    fi
    if [[ $uuid = "" ]]; then
      ARG_CHECK_FLAG=0
      echo
      color_printf red "${RED}请检查参数 [ $declar_opt ] 的值 $disk 是否正确！${NC}" # 如果UUID为空，打印错误信息并退出函数
      echo
    fi
    # 该脚本检查磁盘是否已经在磁盘列表中，如果找到则退出脚本
    local found=false
    for tmp_disk in "${disk_list_tmp[@]}"; do # 检查是否有重复的磁盘
      if [[ $(get_uuid "$disk") = $(get_uuid "$tmp_disk") ]]; then
        found=true
        break
      fi
    done
    if $found; then
      ARG_CHECK_FLAG=0
      echo
      color_printf red "${RED}在参数 [ $declar_opt ] 中检测到重复的磁盘：$disk${NC}" # 如果有重复的磁盘，打印错误信息并退出函数
      echo
    fi
    disk_list_tmp+=("$disk") # 将磁盘加入临时磁盘列表
  done
  #清空磁盘
  if [[ $format_disk = "Y" ]]; then # 是否清空磁盘，如果值是Y，就清空磁盘
    for disk in "${disk_list_tmp[@]}"; do
      dd if=/dev/zero of="$disk" bs=100M count=10 >/dev/null 2>&1 & # 使用dd命令写入0以清空磁盘
      wait $!                                                       # 如果命令执行成功，跳出循环
    done
  fi
}
#==============================================================#
#                    互信检查                                  #
#==============================================================#
function ssh_check() {
  # 检查 ssh 连接是否正常
  local flag=true
  declare -a IPs=("${@:2}") # 远程主机 IP 地址数组
  # 遍历所有 IP 地址，逐个进行 SSH 连接测试
  for ip in "${IPs[@]}"; do
    # 利用 su 命令切换至指定用户执行 ssh 命令，测试连接是否正常
    if ! su - "$1" -c "ssh -q -o ConnectTimeout=1 -o ConnectionAttempts=1 -o PreferredAuthentications=publickey -o StrictHostKeyChecking=no $ip echo 'true'" >/dev/null 2>&1; then
      # 如果某个连接失败，则标识 flag 置为 false，并跳出循环
      flag=false
      break
    fi
  done
  # 输出整体连接状态
  echo $flag
}
#==============================================================#
#                    配置互信                                  #
#==============================================================#
function ssh_trust() {
  # 判断是否已经安装expect工具，如果没有则自动安装
  log_print "配置 OS 用户互信" ""
  if ! command -v expect >/dev/null 2>&1; then
    packages=(
      expect          #配置互信工具
      openssh-clients #SSH客户端
    )
    install_packages_if_not_present "${packages[@]}"
  fi
  # 避免ssh连接远程主机输入 yes/no
  local ssh_config_file="/etc/ssh/ssh_config"
  local config_line="StrictHostKeyChecking no"
  if ! grep -q "^$config_line$" $ssh_config_file; then
    echo "$config_line" >>$ssh_config_file
  fi
  local dest_user=$1             # 远程主机登录用户名
  local passwd=$2                # 远程主机登录密码
  declare -a host_ips=("${@:3}") # 远程主机 IP 地址数组
  local encryption_para=         # 互信加密参数
  # 根据不同用户设置 SSH 目录
  [[ $dest_user = "root" ]] && ssh_dir="/root/.ssh" || ssh_dir="/home/$dest_user/.ssh"
  # 准备 SSH 目录
  rm -rf "$ssh_dir"
  mkdir -p "$ssh_dir" && chmod 700 "$ssh_dir"
  # 生成 SSH 密钥对，并将公钥添加到 authorized_hosts 文件
  ssh-keygen -t rsa -N '' -f "$ssh_dir/id_rsa"
  cat "$ssh_dir/id_rsa.pub" >>"$ssh_dir/authorized_keys" && chmod 600 "$ssh_dir/authorized_keys"
  # 将主机密钥添加到 known_hosts 文件
  if [[ ! -f "$ssh_dir/known_hosts" ]]; then
    # 如果 known_hosts 文件不存在，则创建它
    touch "$ssh_dir/known_hosts"
  fi
  # 互信加密认证
  encryption_para=$([[ $os_version -eq 6 ]] && echo "rsa,dsa" || echo "rsa,dsa,ecdsa,ed25519")
  for ip in "${host_ips[@]}"; do
    # openEuler 22.03 需要增加 ed25519 认证
    ssh-keygen -F "$ip" >/dev/null || ssh-keyscan -t "$encryption_para" "$ip" >>"$ssh_dir/known_hosts" 2>/dev/null
  done
  # 将 SSH 目录分发到远程主机，并设置 SSH 无密码登录
  # 定义expect代码块
  expect_block() {
    /usr/bin/expect <<EOF >/dev/null 2>&1
spawn $1
expect "password:" { sleep 1; send "$passwd\r"; exp_continue } eof { exit }
EOF
  }
  for ip in "${host_ips[@]}"; do
    # 并行传输文件和恢复上下文
    expect_block "scp -q -p -r -P $os_ssh_port $ssh_dir $dest_user@$ip:~"
    expect_block "ssh -q -p $os_ssh_port $dest_user@$ip \"restorecon -RF '$ssh_dir'\"" &
  done
  # 等待所有任务完成
  wait
}
#==============================================================#
#                    数据库启动函数                             #
#==============================================================#
function db_service_cmd() {
  if [[ $os_version -eq 6 ]]; then
    local db_service_path=/etc/rc.d/init.d
  else
    local db_service_path=$env_dm_home/bin
  fi
  # 定义数据库启动的全局变量
  case $dm_install_mode in
  "single" | "datawatch")
    # 数据守护
    DMW_START_CMD="$db_service_path/DmWatcherService${dw_group_name} start"
    DMS_START_CMD="$db_service_path/DmService${db_name} start"
    DMM_START_CMD="$db_service_path/DmMonitorService$dw_group_name start"
    DMW_STOP_CMD="$db_service_path/DmWatcherService${dw_group_name} stop"
    DMS_STOP_CMD="$db_service_path/DmService${db_name} stop"
    DMM_STOP_CMD="$db_service_path/DmMonitorService$dw_group_name stop"
    DMS_RESTART_CMD="$db_service_path/DmService${db_name} restart"
    ;;
  "dsc" | "dscm")
    # DSC集群
    DM_CSS_SERVICE="DmCSSServiceCss"
    if [[ $dm_install_mode == "dsc" ]]; then
      DM_ASM_SERVICE="DmASMSvrServiceAsmsvr"
    else
      DM_ASM_SERVICE="DmASMSvrmServiceAsmsvr"
    fi
    DM_DB_SERVICE="DmService${dsc_db_name}"
    # 定义全局启动服务命令
    CSS_START_CMD="$db_service_path/DmCSSServiceCss start"    # CSS服务启动命令
    ASMSVR_START_CMD="$db_service_path/$DM_ASM_SERVICE start" # ASM实例服务启动命令
    DB_START_CMD="$db_service_path/$DM_DB_SERVICE start"      # DB服务启动命令
    DMC_STOP_CMD="$db_service_path/DmCSSServiceCss stop"      # CSS服务停止命令
    ;;
  esac
}
#==============================================================#
#                    帮助                                      #
#==============================================================#
function help() {
  # 打印参数
  print_options() {
    local options=("$@")
    # 调用 color_printf 函数，输出绿色字体
    # ${option%% *} 表示从 option 变量中删除最后一个空格及其后面的字符，保留前面的部分
    # ${option#* } 表示从 option 变量中删除第一个空格及其前面的字符，保留后面的部分
    for option in "${options[@]}"; do
      color_printf green "${option%% *}" "${option#* }"
    done
  }
  declare -a options=()
  echo
  color_printf blue "用途: 一键部署达梦数据库[单机/数据守护/DMDSC集群]"
  echo
  color_printf red "用法: DMShellInstall [选项] 选项值 { COMMAND | help }"
  echo
  color_printf blue "单机(Single): "
  echo
  options=(
    "-di 达梦数据库安装镜像名称"
    "-kp 达梦数据库密钥路径"
    "-hn 主机名"
    "-dp 系统用户dmdba密码，默认值：[Dameng@123]"
    "-d 数据库软件安装目录，默认值：[/dm]"
    "-dd 数据库文件目录，默认值：[/dmdata]"
    "-ad 数据库归档目录，默认值：[/dmarch]"
    "-bd 数据库备份目录，默认值：[/dmbak]"
    "-dn 数据库名称，默认值：[DAMENG]"
    "-in 数据库实例名称，默认值：[DMSERVER]"
    "-es 数据文件簇大小，默认值：[32]"
    "-ps 数据页大小，默认值：[32]"
    "-cs 字符串大小写敏感，默认值：[Y]"
    "-c 数据库字符集，默认值：[1]"
    "-cm 是否兼容其他数据库模式，取值：0~7，默认值：[0]"
    "-bpm 设置结尾空格填充模式，当等于1时，默认兼容ORACLE，即[-cm 2]，默认值：[0]"
    "-ls 日志文件大小，单位M，默认值：[1024]"
    "-er 是否开启归档模式，默认值：[Y]"
    "-sl 归档空间大小，单位M，默认值：[102400]"
    "-pn 监听端口号，默认值：[5236]"
    "-sp 数据库SYSDBA用户密码，默认值：[SYSDBA]"
    "-bm 数据库备份模式，模式[1]：每天全备，模式[2]：周六全备，周日到周五增备[2]，默认值：[2]"
    "-mp 优化数据库时物理内存占比，默认值：[80]"
    "-om 并发量较高的OLTP数据库参数 [1]，并发量不高的一般业务和OLAP类的数据库参数参数值 [0]，默认值：[0]"
    "-m 仅配置操作系统，默认值：[N]"
    "-ud 仅安装达梦数据库软件，默认值：[N]"
    "-oid 仅初始化数据库，默认值：[N]"
    "-opd 自动优化数据库，默认值：[Y]"
    "-oopd 仅优化数据库，默认值：[N]"
    "-iso 部署集群或时间服务器，需要挂载ISO镜像，脚本自动配置 YUM 源，默认值：[Y]"
    "-ti 时间服务器IP地址[需要配置YUM源]"
  )
  print_options "${options[@]}"
  echo
  color_printf blue "数据守护(DataWatch): "
  echo
  options=(
    "-osp 服务器ssh端口，默认值：[22]"
    "-rp root 用户密码"
    "-hn 主机名前缀，配置每个节点主机名为dw01,dw02...，例如：-hn dw"
    "-dpi DW 实[即]时主备公网IP，以逗号隔开，例如：-dpi 10.211.55.101,10.211.55.102"
    "-api DW 异步主备公网IP，以逗号隔开，例如：-api 10.211.55.103"
    "-dmi DW 所有节点私网IP，以逗号隔开，例如：-dmi 1.1.1.1,1.1.1.2"
    "-dmoi 监视器主机IP，例如：-dmoi 10.211.55.104"
    "-dgn 数据守护组名，默认值：[GRP1]"
    "-mpn 私网监听端口号，取值：1024~65535，默认值：[5336]"
    "-mdpn 守护进程端口号，取值：1024~65535，默认值：[5436]"
    "-midpn 实例监听守护进程端口号，取值：1024~65535，默认值：[5536]"
    "-at 数据守护归档模式，取值：0、1、2，其中REALTIME[0]，TIMELY[1]，默认值：[0]"
    "-awa 数据守护性能模式，取值：0、1，其中高性能模式[0]，事务一致性模式[1]，默认值：[1]"
  )
  print_options "${options[@]}"
  echo
  color_printf blue "DSC集群(DMDSC): "
  echo
  options=(
    "-osp 服务器ssh端口，默认值是：[22]"
    "-rp root 用户密码"
    "-hn 主机名前缀，配置每个节点主机名为dsc01,dsc02...，例如：-hn dsc"
    "-dpi DSC所有节点公网IP，以逗号隔开，例如：-dpi 10.211.55.101,10.211.55.102"
    "-dmi DSC所有节点私网IP，以逗号隔开，例如：-dmi 1.1.1.1,1.1.1.2"
    "-lci DSC第三方确认公网IP，例如：-lci 10.211.55.103"
    "-cdp CSS公网通信端口，取值：1024~65534，默认值：[9341]"
    "-adp ASM公网通信端口，取值：1024~65534，默认值：[9351]"
    "-ddp DB 公网通信端口，取值：1024~65534，默认值：[9361]"
    "-amp ASM私网通信端口，取值：1024~65534，默认值：[9451]"
    "-dmp DB 私网通信端口，取值：1024~65534，默认值：[9461]"
    "-dcd DCR[V]磁盘列表，DSC集群只能传入一块磁盘，例如：/dev/sdb，DSCM集群时，可以传1，3，5块磁盘，例如：/dev/sdb,/dev/sdc,/dev/sdd"
    "-vod VOTE磁盘列表，DSC集群只能传入一块磁盘，例如：/dev/sdc，DSCM集群时，不用传此参数"
    "-lod REDO磁盘列表，可以是一块盘，也可以是多块盘，如果没有redo磁盘，可以不写，例如：/dev/sdd,/dev/sde"
    "-ard ARCH磁盘列表，可以是一块盘，也可以是多块盘，如果没有arch磁盘，可以不写，例如：/dev/sdf,/dev/sdg"
    "-dad DATA磁盘列表，可以是一块盘，也可以是多块盘，盘数必须大于等于 1        ，例如：/dev/sdh,/dev/sdi"
    "-rr REDO镜像文件冗余模式(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1]"
    "-ar ARCH镜像文件冗余模式(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1]"
    "-dr DATA镜像文件冗余模式(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1]"
    "-lgm REDO文件副本数(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1] "
    "-aam ARCH文件副本数(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1] "
    "-dtm SYSTEM/MAIN/ROLL 表空间数据文件副本数(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1] "
    "-ctm dm.ctl 和 dm_service.prikey文件副本数(ASM镜像独有参数)，取值：1、2 或 3；默认值：[1] "
    "-lst REDO日志条带化粒度(ASM镜像独有参数)，取值：0、32、64、128、256，单位 KB。默认值：[64]"
    "-aas ARCH日志条带化粒度(ASM镜像独有参数)，取值：0、32、64、128、256，单位 KB。默认值：[64]"
    "-dst DATA文件条带化粒度(ASM镜像独有参数)，取值：0、32、64、128、256，单位 KB。默认值：[32]"
    "-as 数据分配单元(ASM镜像独有参数)，取值： 1、2、4、8、16、32、64，单位 BYTES。默认值：[4]"
    "-rs ASM磁盘组日志文件大小(ASM镜像独有参数)，取值 0、32、64、128、256，单位 MB。默认值：[128]"
    "-ila 是否配置本地归档，如果配置，默认数据库归档目录 [/dmarch]，可以由参数-ad指定具体目录，默认值：[N]"
    "-fld 过滤重复磁盘，保留输出唯一盘符，参数值为非ASM盘符(系统盘等)，例如：-fld sda，多个盘符用逗号拼接：-fld sda,sdb"
    "-fmd 是否需要格式化共享存储盘，默认值：[Y]"
    "-mtp 是否需要配置multipath多链路聚合，默认值：[N]"
    "-ddn 数据库DB_NAME，默认值：[DSC]"
    "-den 数据库每个节点的实例名前缀，默认值：[DSC]"
    "-apd ASM实例密码，默认值：[Dameng1]"
  )
  print_options "${options[@]}"
  echo
  color_printf red "注意：本脚本仅用于新服务器上实施部署数据使用，严禁在已运行数据库的主机上执行，以免发生数据丢失或者损坏，造成不可挽回的损失！！！"
  echo
  exit 0
}
#==============================================================#
#                    判断传参函数                              #
#==============================================================#
# 判断参数值为空或者-开头
function checkpara_NULL() {
  if [[ -z $2 || $2 == -* ]]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $1 ] 的值是否为空！"
    echo
  fi
}
# 判断参数值是否为 Y/N
function checkpara_YN() {
  if ! [[ $2 =~ ^[YN]$ ]]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $1 ] 的值是否为 [Y/N]."
    echo
  fi
}
# 判断值是否包含
function checkpara_validity() {
  local option_name=$1
  local option_value=$2
  local valid_values=("${@:3}")
  if ! [[ " ${valid_values[*]} " =~ $option_value ]]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "请检查参数 [ $option_name ] 的值 \"$option_value\" 是否为 [ ${valid_values[*]} ] 中的一个！"
    echo
  fi
}
# 判断字符串是否包含另外一个字符串
function check_string() {
  local main_string="$1"
  local substring="$2"
  if [[ $main_string =~ $substring ]]; then
    ARG_CHECK_FLAG=0
    echo
    color_printf red "目录 $main_string 不允许建立在 $substring 目录下！"
    echo
  fi
}
# 判断磁盘冗余模式和磁盘数量关系
function parse_disks() {
  local disk_string="$1"
  local redun_mode="$2"
  local min_disks=1
  case $redun_mode in
  1)
    min_disks=1
    ;;
  2)
    min_disks=2
    ;;
  *)
    min_disks=3
    ;;
  esac
  # 将磁盘字符串按逗号分割为数组
  IFS=',' read -ra disks <<<"$disk_string"
  # 检查磁盘数量是否满足条件
  if ((${#disks[@]} < min_disks)); then
    echo
    color_printf red " ${disks[*]} 磁盘数量不满足冗余模式 $redun_mode 的最小要求！"
    echo
    exit 1
  fi
}
#==============================================================#
#                           校验传参                            #
#==============================================================#
function check_para() {
  # 脚本参数处理
  while [[ $1 ]]; do
    case $1 in
    -node | --node_num)
      node_num=$2
      shift 2
      ;;
    -iso | --iso)
      # 判断ISO参数的值是否为空，如果为空则提示报错退出执行
      checkpara_NULL "$1" "$2"
      # 判断ISO参数的值是否为Y/N，如果不是则提示报错退出执行
      checkpara_YN "$1" "$2"
      iso=$2
      shift 2
      ;;
    -hn | --hostname)
      checkpara_NULL "$1" "$2"
      hostname=$2
      shift 2
      ;;
    -rp | --root_passwd)
      checkpara_NULL "$1" "$2"
      root_passwd=$2
      shift 2
      ;;
    -dp | --dm_passwd)
      checkpara_NULL "$1" "$2"
      dm_passwd=$2
      passwd_check -dp "$dm_passwd"
      shift 2
      ;;
    -dmoi | --dw_monitor_ip)
      checkpara_NULL "$1" "$2"
      dw_monitor_ip=$2
      dw_mode=AUTO
      shift 2
      ;;
    -dpi | --wc_public_ip)
      checkpara_NULL "$1" "$2"
      wc_public_ip=$2
      readarray -t wc_pubip_arr <<<"$(tr ',' '\n' <<<"$wc_public_ip")"
      shift 2
      ;;
    -dmi | --wc_mal_ip)
      checkpara_NULL "$1" "$2"
      wc_mal_ip=$2
      readarray -t wc_malip_arr <<<"$(tr ',' '\n' <<<"$wc_mal_ip")"
      shift 2
      ;;
    -api | --as_public_ip)
      checkpara_NULL "$1" "$2"
      as_public_ip=$2
      readarray -t as_pubip_arr <<<"$(tr ',' '\n' <<<"$as_public_ip")"
      shift 2
      ;;
    -ti | --timeserver_ip)
      checkpara_NULL "$1" "$2"
      timeserver_ip=$2
      check_ip "$timeserver_ip" -ti
      shift 2
      ;;
    -lci | --link_check_ip)
      checkpara_NULL "$1" "$2"
      link_check_ip=$2
      check_ip "$link_check_ip" -lci
      shift 2
      ;;
    -osp | --os_ssh_port)
      checkpara_NULL "$1" "$2"
      os_ssh_port=$2
      shift 2
      ;;
    -d | --env_dm_home)
      checkpara_NULL "$1" "$2"
      env_dm_home=${2%/}
      shift 2
      ;;
    -ad | --arch_dir)
      checkpara_NULL "$1" "$2"
      arch_dir=${2%/}
      shift 2
      ;;
    -bd | --backup_dir)
      checkpara_NULL "$1" "$2"
      backup_dir=${2%/}
      shift 2
      ;;
    -cd | -core_dir)
      checkpara_NULL "$1" "$2"
      core_dir=${2%/}
      shift 2
      ;;
    -dd | --data_dir)
      checkpara_NULL "$1" "$2"
      data_dir=${2%/}
      shift 2
      ;;
    -dn | --db_name)
      checkpara_NULL "$1" "$2"
      db_name=$2
      shift 2
      ;;
    -in | --instance_name)
      checkpara_NULL "$1" "$2"
      instance_name=$2
      shift 2
      ;;
    -es | --extent_size)
      checkpara_NULL "$1" "$2"
      extent_size=$2
      checkpara_validity "$1" "$extent_size" "16" "32" "64"
      shift 2
      ;;
    -ps | --page_size)
      checkpara_NULL "$1" "$2"
      page_size=$2
      checkpara_validity "$1" "$page_size" "4" "8" "16" "32"
      shift 2
      ;;
    -cs | --case_sensitive)
      checkpara_NULL "$1" "$2"
      case_sensitive=$2
      checkpara_validity "$1" "$case_sensitive" "Y" "N" "1" "0"
      shift 2
      ;;
    -c | --charset)
      checkpara_NULL "$1" "$2"
      charset=$2
      checkpara_validity "$1" "$charset" "0" "1" "2"
      shift 2
      ;;
    -cm | --compatible_mode)
      checkpara_NULL "$1" "$2"
      compatible_mode=$2
      checkpara_validity "$1" "$compatible_mode" "0" "1" "2" "3" "4" "5" "6" "7"
      shift 2
      ;;
    -bpm | --blank_pad_mode)
      checkpara_NULL "$1" "$2"
      blank_pad_mode=$2
      checkpara_validity "$1" "$blank_pad_mode" "0" "1"
      if [[ $blank_pad_mode -eq 1 ]]; then
        compatible_mode=2
      fi
      shift 2
      ;;
    -ls | --log_size)
      log_size=$2
      min_log_size=256
      max_log_size=2048
      if ! [[ "$log_size" =~ ^[0-9]+$ && "$log_size" -gt 0 && "$log_size" -ge "$min_log_size" && "$log_size" -le "$max_log_size" ]]; then
        ARG_CHECK_FLAG=0
        echo
        color_printf red "请检查参数 [ -ls ] 的值 $log_size 是否为一个大于零、在 [$min_log_size~$max_log_size] 区间内的整数！"
      fi
      shift 2
      ;;
    -er | --enable_arch)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      enable_arch=$2
      shift 2
      ;;
    -sl | --space_limit)
      checkpara_NULL "$1" "$2"
      space_limit=$2
      if [[ "$space_limit" =~ ^[0-9]+$ && $space_limit -le 10240 ]]; then
        ARG_CHECK_FLAG=0
        echo
        color_printf red "请检查参数 [ -sl ] 的值 \"$space_limit\" 是否大于 10240 的正整数！"
      fi
      shift 2
      ;;
    -pn | --port_num)
      checkpara_NULL "$1" "$2"
      port_num=$2
      shift 2
      ;;
    -sp | --sysdba_pwd)
      checkpara_NULL "$1" "$2"
      sysdba_pwd=$2
      ## 校验数据库密码
      [[ $sysdba_pwd != "SYSDBA" ]] && passwd_check -sp "$sysdba_pwd"
      shift 2
      ;;
    -bm | --backup_mode)
      checkpara_NULL "$1" "$2"
      backup_mode=$2
      checkpara_validity "$1" "$backup_mode" "1" "2"
      shift 2
      ;;
    -m | --only_conf_os)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      only_conf_os=$2
      shift 2
      ;;
    -ud | --install_until_db)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      install_until_db=$2
      shift 2
      ;;
    -oid | --only_init_db)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      only_init_db=$2
      shift 2
      ;;
    -opd | --optimize_db)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      optimize_db=$2
      shift 2
      ;;
    -oopd | --only_optimize_db)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      only_optimize_db=$2
      shift 2
      ;;
    -mp | --mem_percent)
      checkpara_NULL "$1" "$2"
      mem_percent=$2
      shift 2
      ;;
    -om | --oltp_mode)
      checkpara_NULL "$1" "$2"
      oltp_mode=$2
      shift 2
      ;;
    -di | --dm_iso_name)
      checkpara_NULL "$1" "$2"
      dm_iso_name=$2
      shift 2
      ;;
    -kp | --key_path)
      checkpara_NULL "$1" "$2"
      key_path=$2
      shift 2
      ;;
    -dgn | --dw_group_name)
      checkpara_NULL "$1" "$2"
      dw_group_name=$2
      shift 2
      ;;
    -mpn | --mal_port_num)
      checkpara_NULL "$1" "$2"
      mal_port_num=$2
      shift 2
      ;;
    -mdpn | --mal_dw_port_num)
      checkpara_NULL "$1" "$2"
      mal_dw_port_num=$2
      shift 2
      ;;
    -midpn | --mal_inst_dw_port_num)
      checkpara_NULL "$1" "$2"
      mal_inst_dw_port_num=$2
      shift 2
      ;;
    -oguid | --oguid)
      oguid=$2
      shift 2
      ;;
    -dim | --dm_install_mode)
      dm_install_mode=$2
      shift 2
      ;;
    -at | --arch_type)
      checkpara_NULL "$1" "$2"
      arch_type=$2
      checkpara_validity "$1" "$arch_type" "0" "1"
      case $arch_type in
      0)
        dw_arch_type=REALTIME
        ;;
      1)
        dw_arch_type=TIMELY
        ;;
      esac
      shift 2
      ;;
    -ri | --repeat_interval)
      checkpara_NULL "$1" "$2"
      repeat_interval=$2
      if ! [[ "$repeat_interval" =~ ^[0-9]+$ && "$repeat_interval" -gt 0 ]]; then
        ARG_CHECK_FLAG=0
        echo
        color_printf red "请检查参数 [ -rs ] 的值 $repeat_interval 是否为一个大于零的整数！"
      fi
      shift 2
      ;;
    -awa | --arch_wait_apply)
      checkpara_NULL "$1" "$2"
      arch_wait_apply=$2
      checkpara_validity "$1" "$arch_wait_apply" "0" "1"
      shift 2
      ;;
    -ddn | --dsc_db_name)
      checkpara_NULL "$1" "$2"
      dsc_db_name=$2
      shift 2
      ;;
    -den | --dsc_ep_name)
      checkpara_NULL "$1" "$2"
      dsc_ep_name=$2
      shift 2
      ;;
    -fmd | --format_disk)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      format_disk=$2
      shift 2
      ;;
    -dcd | --dcr_disk)
      checkpara_NULL "$1" "$2"
      dcr_disk=$2
      shift 2
      ;;
    -vod | --vote_disk)
      checkpara_NULL "$1" "$2"
      vote_disk=$2
      shift 2
      ;;
    -lod | --log_disk)
      checkpara_NULL "$1" "$2"
      log_disk=$2
      shift 2
      ;;
    -ard | --arch_disk)
      checkpara_NULL "$1" "$2"
      arch_disk=$2
      shift 2
      ;;
    -dad | --data_disk)
      checkpara_NULL "$1" "$2"
      checkpara_NULL "$1" "$2"
      data_disk=$2
      shift 2
      ;;
    -as | --au_size)
      au_size=$2
      checkpara_validity "$1" "$au_size" "1" "2" "4" "8" "16" "32" "64"
      shift 2
      ;;
    -rs | redo_size)
      redo_size=$2
      min_redo_size=64
      max_redo_size=2048
      if ! [[ "$redo_size" =~ ^[0-9]+$ && "$redo_size" -gt 0 && "$redo_size" -ge "$min_redo_size" && "$redo_size" -le "$max_redo_size" ]]; then
        ARG_CHECK_FLAG=0
        echo
        color_printf red "请检查参数 [ -rs ] 的值 $redo_size 是否为一个大于零、在 [$min_redo_size~$max_redo_size] 区间内的整数！"
      fi
      shift 2
      ;;
    -rr | --redo_redun)
      checkpara_NULL "$1" "$2"
      redo_redun=$2
      checkpara_validity "$1" "$redo_redun" "1" "2" "3"
      shift 2
      ;;
    -ar | --arch_redun)
      checkpara_NULL "$1" "$2"
      arch_redun=$2
      checkpara_validity "$1" "$redo_redun" "1" "2" "3"
      shift 2
      ;;
    -dr | --data_redun)
      checkpara_NULL "$1" "$2"
      data_redun=$2
      checkpara_validity "$1" "$redo_redun" "1" "2" "3"
      shift 2
      ;;
    -ctm | --ctl_mirror)
      checkpara_NULL "$1" "$2"
      ctl_mirror=$2
      checkpara_validity "$1" "$ctl_mirror" "1" "2" "3"
      shift 2
      ;;
    -dtm | --data_mirror)
      checkpara_NULL "$1" "$2"
      data_mirror=$2
      checkpara_validity "$1" "$data_mirror" "1" "2" "3"
      shift 2
      ;;
    -lgm | --log_mirror)
      checkpara_NULL "$1" "$2"
      log_mirror=$2
      checkpara_validity "$1" "$log_mirror" "1" "2" "3"
      shift 2
      ;;
    -aam | --arch_asm_mirror)
      checkpara_NULL "$1" "$2"
      arch_asm_mirror=$2
      checkpara_validity "$1" "$arch_asm_mirror" "1" "2" "3"
      shift 2
      ;;
    -dst | --data_striping)
      checkpara_NULL "$1" "$2"
      data_striping=$2
      checkpara_validity "$1" "$data_striping" "0" "32" "64" "128" "256"
      shift 2
      ;;
    -lst | --log_striping)
      checkpara_NULL "$1" "$2"
      log_striping=$2
      checkpara_validity "$1" "$log_striping" "0" "32" "64" "128" "256"
      shift 2
      ;;
    -aas | --arch_asm_striping)
      checkpara_NULL "$1" "$2"
      arch_asm_striping=$2
      checkpara_validity "$1" "$arch_asm_striping" "0" "32" "64" "128" "256"
      shift 2
      ;;
    -ila | --is_local_arch)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      is_local_arch=$2
      shift 2
      ;;
    -apd | --asm_pwd)
      checkpara_NULL "$1" "$2"
      asm_pwd=$2
      shift 2
      ;;
    -cdp | --css_dcr_ep_port)
      checkpara_NULL "$1" "$2"
      css_dcr_ep_port=$2
      shift 2
      ;;
    -adp | --asm_dcr_ep_port)
      checkpara_NULL "$1" "$2"
      asm_dcr_ep_port=$2
      shift 2
      ;;
    -ddp | --db_dcr_ep_port)
      checkpara_NULL "$1" "$2"
      db_dcr_ep_port=$2
      shift 2
      ;;
    -amp | --asvrmal_mal_port)
      checkpara_NULL "$1" "$2"
      asvrmal_mal_port=$2
      shift 2
      ;;
    -dmp | --db_mal_port)
      checkpara_NULL "$1" "$2"
      db_mal_port=$2
      shift 2
      ;;
    -mtp | --multipath)
      checkpara_NULL "$1" "$2"
      checkpara_YN "$1" "$2"
      multipath=$2
      shift 2
      ;;
    -fld | --fil_disk)
      checkpara_NULL "$1" "$2"
      fil_disk=$2
      filter_disk
      shift 2
      exit 1
      ;;
    -h | --help) help ;;
    *)
      echo
      color_printf red "脚本已退出安装，请检查是否参数是否正确，可以查看脚本帮助：'DMShellInstall --help' 获取更多信息！"
      echo
      exit 1
      ;;
    esac
  done
  # 仅在安装数据库时候判断
  if [[ $only_init_db == "N" ]]; then
    # 定义目录数组
    directories=("$arch_dir" "$backup_dir" "$core_dir" "$data_dir")
    # 循环检查目录数组中的每个目录
    for dir in "${directories[@]}"; do
      check_string "$dir" "$env_dm_home"
    done
  else
    # 以用户 dmdba 运行数据库命令，并检查退出状态
    su - dmdba -c "$env_dm_home/bin/disql -id" >/dev/null 2>&1
    # 检查退出状态
    # shellcheck disable=SC2181
    if [ $? -ne 0 ]; then
      echo
      color_printf red " $env_dm_home 目录下未安装数据库，请先安装数据库软件或者指定正确的 -d 参数。"
      echo
      exit 1
    fi
  fi
}
#==============================================================#
#                         选择数据库安装模式                      #
#==============================================================#
function select_install_mode() {
  if [[ $node_num -eq 1 ]]; then
    if [[ -z $dm_install_mode ]]; then
      while :; do
        echo
        read -rep "$(echo -e "\033[34m请选择达梦数据库部署类型：单机[si]/数据守护[dw]/dsc集群[dsc]/dsc集群(ASM镜像)[dscm]: \E[0m")" dm_install_mode
        echo
        case "$dm_install_mode" in
        si) dm_install_mode=single ;;
        dw) dm_install_mode=datawatch ;;
        dsc) dm_install_mode=dsc ;;
        esac
        if [[ $dm_install_mode =~ ^(single|datawatch|dsc|dscm)$ ]]; then
          color_printf green "达梦数据库安装部署类型: " "$dm_install_mode"
          break
        else
          color_printf red "达梦数据库部署类型选择错误，请重新输入 si 或者 dw 或者 dsc 或者 dscm ！"
          echo
        fi
      done
    else
      while :; do
        case "$dm_install_mode" in
        si) dm_install_mode=single ;;
        dw) dm_install_mode=datawatch ;;
        dsc) dm_install_mode=dsc ;;
        esac
        if [[ $dm_install_mode =~ ^(single|datawatch|dsc|dscm)$ ]]; then
          break
        else
          color_printf red "达梦数据库部署类型选择错误，请重新输入 si 或者 dw 或者 dsc 或者 dscm ！"
          echo
        fi
      done
    fi
  fi
}
#==============================================================#
#                    安装数据库                                #
#==============================================================#
function begin_install_dmdb() {
  # 接收参数校验
  args_array=("${@}")
  # 安装 DW/DSC 时，多节点互信需要配置 yum 源，判断是否需要挂载操作系统 ISO 镜像，如果需要挂载则自动配置 yum 源；如果不需要挂载，则默认自行配置 yum 源
  if [[ $node_num -eq 1 ]]; then
    case $dm_install_mode in
    "single")
      if [[ -n $timeserver_ip ]]; then
        if [[ $iso = "Y" ]]; then
          conf_yum
        fi
      fi
      # 判断仅优化数据库传dm_home,sysdba_pwd,port_num值
      if [[ $only_optimize_db == "Y" && $optimize_db == "Y" ]]; then
        # 设置标志变量
        d_flag=false
        sp_flag=false
        pn_flag=false
        # 遍历所有参数
        for arg in "${args_array[@]}"; do
          case $arg in
          -d) d_flag=true ;;
          -sp) sp_flag=true ;;
          -pn) pn_flag=true ;;
          esac
        done
        # 检查是否所有标志变量都为 true
        if [ "$d_flag" == false ] || [ "$sp_flag" == false ] || [ "$pn_flag" == false ]; then
          echo
          color_printf red "脚本仅优化数据库必须设置以下参数: -d, -sp, -pn. 请键入 'sh DMShellInstall --help' 获取更多帮助！"
          echo
          exit 1
        fi
      fi
      ;;
    "datawatch")
      if [[ ! $root_passwd && ! $wc_mal_ip && (! $wc_public_ip || ! $as_public_ip) ]]; then
        echo
        color_printf red "数据守护集群安装模式必须设置以下参数: -rp, -dpi, -dmi. 请键入 'sh DMShellInstall --help' 获取更多帮助！"
        echo
        exit 1
      fi
      # 判断节点上限数量
      if [[ ${#wc_malip_arr[@]} -lt 2 || ${#wc_malip_arr[@]} -gt 9 ]]; then
        echo
        color_printf red "请检查参数 [ $1 ] $wc_public_ip ，至少需要 2 个节点，最多支持 9 个节点！"
        echo
        exit 1
      fi
      # 安装数据守护
      if [[ $iso = "Y" ]]; then
        conf_yum
      fi
      install_dw
      ;;
    "dsc" | "dscm")
      if [[ $dm_install_mode == "dsc" ]]; then
        if [[ ! $root_passwd || ! $wc_public_ip || ! $wc_mal_ip || ! $dcr_disk || ! $vote_disk || ! $data_disk ]]; then
          echo
          color_printf red "DSC集群安装模式必须设置以下参数: -rp, -dpi, -dmi, -dcd, -vod, -dad. 请键入 'sh DMShellInstall --help' 获取更多帮助！"
          echo
          exit 1
        fi
      else
        if [[ ! $root_passwd || ! $wc_public_ip || ! $wc_mal_ip || ! $dcr_disk || $vote_disk || ! $data_disk ]]; then
          echo
          color_printf red "DSCM集群安装模式必须设置以下参数: -rp, -dpi, -dmi, -dcd, -dad. 请键入 'sh DMShellInstall --help' 获取更多帮助！"
          echo
          exit 1
        fi
        count=0
        # 将字符串按逗号分割成数组
        IFS=',' read -ra disks <<<"$dcr_disk"
        # 遍历数组，统计元素个数
        for disk in "${disks[@]}"; do
          ((count++))
        done
        if ! [[ $count =~ ^(1|3|5)$ ]]; then
          echo
          color_printf red "DSCM集群参数[ -dcd ]值，只能是包含1，3，5块磁盘，请重新传 -dcd 参数！"
          echo
          exit 1
        fi
        # 声明一个数组来存储磁盘的冗余模式
        declare -a disk_redun=(${log_disk:+"$redo_redun"} ${arch_disk:+"$arch_redun"} "$data_redun")
        # 遍历磁盘数组，从 1 开始，对每个磁盘调用 parse_disks 函数来解析并检查冗余模式
        for ((x = 1; x < ${#disk_arr[@]}; x++)); do
          parse_disks "${disk_arr[$x]}" "${disk_redun[$x - 1]}"
        done
      fi
      # 判断节点上限数量
      if [[ ${#wc_malip_arr[@]} -lt 2 || ${#wc_malip_arr[@]} -gt 16 ]]; then
        echo
        color_printf red "请检查参数 [ $1 ] $wc_public_ip ，至少需要 2 个节点，最多支持 16 个节点！"
        echo
        exit 1
      fi
      # 安装DSC集群
      if [[ $iso = "Y" ]]; then
        conf_yum
      fi
      install_dsc
      ;;
    esac
  else
    if [[ $dm_install_mode == "datawatch" || $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
      if [[ -n $timeserver_ip || (($multipath == "Y" && ${disk_list_arr[0]} =~ "dev")) ]]; then
        if [[ $iso = "Y" ]]; then
          conf_yum
        fi
      fi
    fi
  fi
}
#==============================================================#
#                    打印环境信息                              #
#==============================================================#
function print_sysinfo() {
  log_print "打印系统信息" ""
  print_cpu_info() {
    # 保存当前语言环境
    current_lang=$LANG
    # 临时切换到英文环境
    export LANG="en_US.UTF-8"
    # 定义关联数组的键的顺序
    keys=("A" "B" "C" "D")
    # 保存要提取的信息的关键词
    declare -A keywords=(
      ["A"]="$(grep </proc/cpuinfo "model name" | head -n 1 | awk -F ': ' '{print $2}')"
      ["A_DESC"]="型号名称                "
      ["B"]="$(grep </proc/cpuinfo "physical id" | sort | uniq | wc -l)"
      ["B_DESC"]="物理 CPU 个数           "
      ["C"]="$(grep </proc/cpuinfo "core id" | sort -u | wc -l)"
      ["C_DESC"]="每个物理 CPU 的逻辑核数 "
      ["D"]="$(grep -c "processor" /proc/cpuinfo)"
      ["D_DESC"]="系统的 CPU 线程数       "
    )
    # 循环提取信息并按顺序打印
    for key in "${keys[@]}"; do
      desc="${keywords[${key}_DESC]}"
      value="${keywords[$key]}"
      color_printf white "$desc ：$value"
    done
    # 恢复原来的语言环境
    export LANG="$current_lang"
  }
  # 服务器时间
  color_printf blue "服务器时间: "
  echo
  date
  # 操作系统版本
  echo
  color_printf blue "操作系统版本: "
  echo
  if check_file /etc/os-release; then
    cat /etc/os-release
  elif check_file /etc/system-release; then
    cat /etc/system-release
  elif check_file /etc/redhat-release; then
    cat /etc/redhat-release
  fi
  # 内核信息
  echo
  color_printf blue "内核信息: "
  echo
  cat /proc/version
  # 服务器属性
  echo
  color_printf blue "服务器属性: "
  echo
  virt-what
  # cpu信息
  echo
  color_printf blue "cpu信息: "
  echo
  print_cpu_info
  # 内存信息
  echo
  color_printf blue "内存信息: "
  echo
  free -m && free -h
  # 挂载信息
  echo
  color_printf blue "挂载信息: "
  echo
  grep </etc/fstab -E -v '^#|^\*#|^$'
  # 目录信息
  echo
  color_printf blue "目录信息: "
  echo
  df -h
}
#==============================================================#
#                    配置YUM源                                 #
#==============================================================#
function conf_yum() {
  # 截取ISO镜像挂载路径
  local -r mountPatch=$(mount | awk '/iso9660/ && !/dm8/ && !/run\/media/ {print $3}')
  if [[ -z $mountPatch ]]; then
    echo
    color_printf red "操作系统 ISO 镜像未挂载，且 -iso 参数配置为 Y，请先挂载操作系统镜像！"
    echo
    exit 1
  fi
  local repo_path=/etc/yum.repos.d
  local repo_name="${repo_path}"/local.repo
  # 避免Linux主机提示注册
  [[ -e /etc/yum/pluginconf.d/subscription-manager.conf ]] && sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/subscription-manager.conf
  # 避免Kyli/openEuler主机提示注册
  [[ -e /etc/yum/pluginconf.d/debuginfo-install.conf ]] && sed -i 's/enabled=1/enabled=0/' /etc/yum/pluginconf.d/debuginfo-install.conf
  # 备份原有的yum配置文件
  # 备份原有YUM配置文件
  mkdir "${repo_path}"/bak -p && nohup mv "$repo_path"/* "$repo_path"/bak >/dev/null 2>&1
  # 根据不同版本号创建本地YUM源配置文件
  # 定义基础OS源
  baseos() {
    cat <<-EOF >"${repo_name}"
[BaseOS]
name=BaseOS
baseurl=$baseurl_file
enabled=1
gpgcheck=0
EOF
  }
  # 定义软件源
  appstream() {
    cat <<-EOF >>"${repo_name}"
[AppStream]
name=AppStream
baseurl=file://$mountPatch/AppStream
enabled=1
gpgcheck=0
EOF
  }
  # 适配各种OS系统配置YUM源
  case $os_flag in
  OS1)
    local baseurl_file="file://$mountPatch"
    baseos
    ;;
  OS2)
    local baseurl_file="file://$mountPatch/BaseOS"
    baseos
    appstream
    ;;
  OS3)
    local baseurl_file="file://$mountPatch"
    baseos
    appstream
    ;;
  OS4)
    cat <<-EOF >"${repo_name}"
[openEuler]
name=openEuler
baseurl=file://$mountPatch
enabled=1
gpgcheck=1
gpgkey=file://$mountPatch/RPM-GPG-KEY-openEuler
EOF
    ;;
  esac
  log_print "打印 yum 配置文件内容" ""
  # 打印 yum 配置文件内容
  cat "${repo_name}"
  # 清除旧的yum源，重建新的yum源
  yum clean all >/dev/null 2>&1 && yum makecache >/dev/null 2>&1
}
#==============================================================#
#                    禁用防火墙                                 #
#==============================================================#
# 此函数用于禁用 Linux 系统中的防火墙
function conf_firewall() {
  # 输出一条日志消息表示正在禁用防火墙
  log_print "禁用防火墙" ""
  if [[ $os_version -eq 6 ]]; then
    # 如果操作系统版本为 RHEL 6，则通过 service 命令停止 iptables 和 ip6tables 服务并禁用开机启动
    if service iptables status >/dev/null 2>&1; then
      service iptables stop
      service ip6tables stop
    else
      echo "防火墙服务未启动，无需禁用！"
      return
    fi
    chkconfig iptables off
    chkconfig ip6tables off
    service iptables status
  else
    # 如果操作系统版本不是 RHEL 6，则通过 systemctl 命令停止 firewalld 服务并禁用开机启动
    if [[ $(systemctl status firewalld.service | grep -c running) -gt 0 ]]; then
      systemctl stop firewalld.service
    else
      echo "防火墙服务未启动，无需禁用！"
      return
    fi
    systemctl disable firewalld.service -q
    systemctl status firewalld -q
  fi
}
#==============================================================#
#                    禁用 selinux                              #
#==============================================================#
# 此函数用于禁用 SELinux
function conf_selinux() {
  # 输出一条日志消息表示正在禁用 SELinux，并备份 /etc/selinux/config 配置文件
  log_print "禁用 SELinux" ""
  local config_file="/etc/selinux/config"
  bak_file "$config_file"
  # 获取selinux状态
  status=$(getenforce)
  # 判断selinux状态
  case "$status" in
  "Disabled")
    # 如果 SELinux 已经被禁用，则输出一条消息表示 SELinux 已经被禁用
    echo "SELinux 已经被禁用"
    ;;
  "Enforcing")
    # 如果 SELinux 正在强制执行，则将其设置为禁用，并将 /etc/selinux/config 文件中的 SELINUX 属性设置为 disabled
    echo "SELinux 正在强制执行，现在将其设置为禁用"
    setenforce 0
    sed -i 's/SELINUX=.*/SELINUX=disabled/' "$config_file"
    echo "SELinux 已被设置为禁用，请重启系统以使更改生效"
    ;;
  *)
    # 如果 SELinux 是宽容模式，则将 /etc/selinux/config 文件中的 SELINUX 属性设置为 disabled
    echo "SELinux 是宽容模式"
    sed -i 's/SELINUX=.*/SELINUX=disabled/' "$config_file"
    echo "SELinux 已被设置为禁用，请重启系统以使更改生效"
    ;;
  esac
}
#==============================================================#
#                    关闭 SWAP 功能                             #
#==============================================================#
# 此函数用于禁用 Linux 系统中的 SWAP 分区
function conf_swap() {
  # 输出一条日志消息表示正在关闭 SWAP 功能
  log_print "调整 SWAP 分区" ""
  local fstab_file="/etc/fstab"
  # 获取系统中 SWAP 分区的大小并判断是否存在 SWAP 分区
  local -r swap=$(free | grep Swap | awk '{print $2}')
  if [[ -z $swap ]]; then
    # 如果不存在 SWAP 分区，则输出一条消息表示无需关闭 SWAP 功能
    color_printf yellow "系统中不存在 SWAP 分区，无需调整 SWAP 分区！"
  else
    # 如果物理内存大于 40 * 1024 * 1024 = 41943040 时，关闭swap
    if [[ $os_memory -gt 41943040 ]]; then
      # 如果存在 SWAP 分区，则关闭 SWAP、备份 /etc/fstab 文件，并使用 sed 命令注释掉 /etc/fstab 文件中包含 "swap" 的行
      swapoff -a
      bak_file "$fstab_file"
      sed -ri 's/.*swap.*/#&/' "$fstab_file"
    fi
    # 输出当前系统内存的使用情况并检查 /etc/fstab 文件中是否有包含 "swap" 的行
    free -m
    echo
    grep <"$fstab_file" -v "^$" | grep "swap"
  fi
}
#==============================================================#
#                    禁用透明大页&禁用NUMA&开启I/0 schedule      #
#==============================================================#
#此函数用于禁用透明大页，禁用numa，磁盘调度
function conf_grub() {
  # 添加注释和变量
  log_print "禁用透明大页 & 禁用NUMA & 开启 I/0 schedule" ""
  local transparent_hugepage="transparent_hugepage=never"
  local elevator="elevator=deadline"
  local numa="numa=off"
  # 添加函数和日志记录
  update_parameter() {
    local parameter=$1
    if [[ $(grubby --info=ALL | grep -c "$parameter") -eq 0 ]]; then
      grubby --update-kernel=ALL --args="$parameter"
    fi
  }
  local options=("${transparent_hugepage}" "${elevator}" "${numa}")
  for option in "${options[@]}"; do
    update_parameter "$option"
  done
  grubby --info=ALL | awk '/numa/{print $0 "\n-" $(NR-1) "\n-" $(NR-2)}'
}
#==============================================================#
#                    创建DMDBA用户                              #
#==============================================================#
# 此函数用于创建dmdba操作系统用户
function create_user() {
  # 添加注释和变量
  log_print "创建 DMDBA 用户" ""
  local dinstall_group="dinstall"
  local dmdba_group="dmdba"
  local dmdba_user="dmdba"
  # 添加函数
  create_group() {
    local group=$1
    if [[ $(grep -E -c "$group" /etc/group) -eq 0 ]]; then
      /usr/sbin/groupadd -g "$2" "$group"
    fi
  }
  create_user() {
    local user=$1
    if [[ $(grep -E -c "$user" /etc/passwd) -eq 0 ]]; then
      /usr/sbin/useradd -u 56781 -g $dinstall_group -G $dmdba_group -s /bin/bash -d /home/"$user" -m "$user"
    else
      /usr/sbin/usermod -g $dinstall_group -G $dmdba_group -s /bin/bash -d /home/"$user" -m "$user"
    fi
  }
  create_group $dinstall_group 56781
  create_group $dmdba_group 56782
  create_user $dmdba_user
  echo "$dmdba_user:$dm_passwd" | /usr/sbin/chpasswd
  grep </etc/passwd $dmdba_user
  echo
  id $dmdba_user
}
#==============================================================#
#                    创建安装目录                               #
#==============================================================#
# 此函数用于创建目录并配置权限
function create_dir() {
  # 检查 env_dm_home 目录是否存在
  if [[ -d "$env_dm_home" ]]; then
    # 备份 env_dm_home
    if pgrep -f "dmap|dmserver" >/dev/null 2>&1; then
      # shellcheck disable=SC2154
      if [[ "$os_version" -ne 6 ]]; then
        env_dm_home=$(pgrep -af "dmap|dmserver" | awk '{print $2}' | xargs dirname | sed 's|/bin.*$||' | head -1)
      else
        # shellcheck disable=SC2009
        env_dm_home=$(ps -ef | grep "dmap\|dmserver" | grep -v grep | awk '{print $NF}' | sed 's|/bin.*$||' | head -1)
      fi
    else
      if [[ "$(ls -A "$env_dm_home")" ]]; then
        mv "$env_dm_home" "${env_dm_home}${current}"
      fi
      # 创建 env_dm_home 目录
      mkdir -p "$env_dm_home"
      chmod 775 "$env_dm_home"
      chown dmdba:dinstall "$env_dm_home"
    fi
  fi
  # 检查并重命名 data_dir、backup_dir 和 arch_dir 子目录（如果存在）
  for dir in "$data_dir" "$backup_dir" "$arch_dir"; do
    if [[ -d "$dir" ]]; then
      # 如果数据库正在运行，则不修改目录
      if [[ -d "$dir/$(upper "$db_name")" ]] && ! pgrep -f "$(upper "$db_name")/dm.ini" >/dev/null 2>&1; then
        mv "$dir/$(upper "$db_name")" "$dir/$(upper "$db_name")""$current"
      elif [[ -d "$dir/$(upper "$dsc_db_name")" ]]; then
        mv "$dir/$(upper "$dsc_db_name")" "$dir/$(upper "$dsc_db_name")""$current"
      fi
    else
      mkdir -p "$dir"
    fi
  done
  # 创建dmcore目录
  mkdir -p "$core_dir"
  # 如果 scripts_dir 目录不存在，则创建它
  test -d "$scripts_dir" || mkdir -p "$scripts_dir"
  # 确认监视器创建参数文件存放目录
  if [[ $dm_install_mode == "datawatch" && $node_num -eq -1 ]]; then
    test -d "$data_dir"/"$db_name" || mkdir -p "$data_dir"/"$db_name"
  fi
  # 如果 dm_install_mode 变量等于 "dsc"，则在 data_dir 目录中创建一个名为 $(upper "$dsc_db_name") 的子目录
  if [[ $dm_install_mode == "dsc" || $dm_install_mode = "dscm" ]]; then
    mkdir -p "$data_dir"/"$(upper "$dsc_db_name")"
  fi
  # 设置所有目录的所有权和权限
  chown -R dmdba:dinstall "$data_dir" "$backup_dir" "$arch_dir" "$scripts_dir" "$core_dir"
  chmod 775 "$data_dir" "$backup_dir" "$arch_dir" "$scripts_dir" "$core_dir"
}
#==============================================================#
#                    配置内核参数和资源                          #
#==============================================================#
# 此函数用于配置操作系统内核参数
function conf_sysctl() {
  log_print "配置内核参数和资源" ""
  local sysctl_file="/etc/sysctl.conf"
  # 获取系统页面大小，用于计算内存总量
  # shellcheck disable=SC2155
  local pagesize=$(getconf PAGE_SIZE)
  # 计算内存总量（以 KB 或字节为单位）
  # shellcheck disable=SC2219
  let shmall=$(((os_memory - 1) * 1024 / pagesize))
  # shellcheck disable=SC2219
  let shmmax=$((os_memory * 1024 - 1))
  # 如果 shmall 小于 2097152，则将其设为 2097152
  ((shmall < 2097152)) && shmall=2097152
  # 如果 shmmax 小于 4294967295，则将其设为 4294967295
  ((shmmax < 4294967295)) && shmmax=4294967295
  bak_file "$sysctl_file"
  #修改/etc/sysctl.conf文件
  params=(
    "# DMBegin"
    "fs.aio-max-nr = 1048576"                                                      # 最大异步 I/O 事件数
    "fs.file-max = 6815744"                                                        # 系统可分配的最大文件句柄数
    "fs.nr_open = 20480000"                                                        # 系统范围内可打开的最大文件数
    "kernel.core_pattern = $core_dir/core.%e.%p.%t"                                # Core dump 文件名模式
    "kernel.panic_on_oops = 1"                                                     # Oops（内核恐慌）时内核行为
    "kernel.numa_balancing = 0"                                                    # 出于性能考虑禁用 NUMA 平衡
    "kernel.randomize_va_space = 2"                                                # 地址空间布局随机化（ASLR）安全特性
    "kernel.shmall = $shmall"                                                      # 系统范围内共享内存的最大总量
    "kernel.shmmax = $shmmax"                                                      # 单个共享内存段的最大大小
    "kernel.shmmni = 4096"                                                         # 可创建的最大共享内存段数
    "kernel.sem = 250 32000 100 128"                                               # 信号量调整参数：semmsl、semmns、semopm 和 semmni
    "net.ipv4.ip_local_port_range = 9000 65500"                                    # 出站连接的本地端口范围
    "net.ipv4.tcp_retries2 = 3"                                                    # TIME-WAIT 状态下 TCP 段的最大重试次数
    "net.ipv4.tcp_fin_timeout = 5"                                                 # TCP 最终数据包的超时时间
    "net.ipv4.tcp_synack_retries = 2"                                              # 被动 TCP 连接的 SYNACK 数据包的重试次数
    "net.ipv4.tcp_syncookies = 1"                                                  # 启用 TCP SYN cookies 以防止 SYN 洪水攻击
    "net.ipv4.tcp_timestamps = 1"                                                  # 启用 TCP 时间戳以进行更好的往返时间测量
    "net.ipv4.tcp_rmem = 8192 87380 16777216"                                      # TCP 接收缓冲区内存分配
    "net.ipv4.tcp_wmem = 8192 65536 16777216"                                      # TCP 发送缓冲区内存分配
    "net.core.rmem_default = 262144"                                               # 所有网络接口接收缓冲区大小的默认值
    "net.core.rmem_max = 4194304"                                                  # 所有网络接口接收缓冲区大小的最大值
    "net.core.wmem_default = 262144"                                               # 所有网络接口发送缓冲区大小的默认值
    "net.core.wmem_max = 1048576"                                                  # 所有网络接口发送缓冲区大小的最大值
    "vm.swappiness = 10"                                                           # 控制内核倾向于交换匿名内存的程度
    "vm.min_free_kbytes = $(free -k | awk 'NR==2 {printf "%d\n", int($2 / 200)}')" # 触发交换所需的最小空闲内核内存量
    "vm.numa_stat = 0"                                                             # 报告 NUMA 内存本地性统计
    "vm.overcommit_memory = 0"                                                     # 启用内存过度承诺
    "vm.zone_reclaim_mode = 0"                                                     # 控制内存区域的内存回收行为
    "# DMEnd"
  )
  printf "%s\n" "${params[@]}" | tee -a "$sysctl_file" >/dev/null
  sysctl -p
}
#==============================================================#
#                    配置系统资源                               #
#==============================================================#
# 此函数用于配置操作系统资源参数
function conf_limits() {
  log_print "配置系统资源" ""
  local limits_file="/etc/security/limits.conf"
  # 备份文件
  bak_file "$limits_file"
  # 修改/etc/security/limits.conf文件
  params=(
    "# DMBegin"
    # 设置核心转储文件大小限制为0
    "*     hard core 0"
    "*     soft core 0"
    "dmdba soft core unlimited"
    "dmdba hard core unlimited"
    # 设置进程数限制为10240
    "dmdba soft nproc 10240"
    "dmdba hard nproc 10240"
    # 设置文件描述符限制为65536
    "dmdba soft nofile 65536"
    "dmdba hard nofile 65536"
    # 设置数据段大小限制为无限制
    "dmdba hard data unlimited"
    "dmdba soft data unlimited"
    # 设置文件大小限制为无限制
    "dmdba hard fsize unlimited"
    "dmdba soft fsize unlimited"
    # 设置堆栈大小限制为65536
    "dmdba soft stack  65536"
    "dmdba hard stack  65536"
    "# DMEnd"
  )
  printf "%s\n" "${params[@]}" | tee -a "$limits_file" >/dev/null
  sed -n '/# DMBegin/,/# DMEnd/p' "$limits_file" | grep -v '^#|^*#|^$'
  echo
  # 修改/etc/pam.d/login文件
  local login_file="/etc/pam.d/login"
  bak_file "$login_file"
  params=(
    "# DMBegin"
    "session required pam_limits.so"
    "session required /lib64/security/pam_limits.so"
    "# DMEnd"
  )
  printf "%s\n" "${params[@]}" | tee -a "$login_file" >/dev/null
  sed -n '/# DMBegin/,/# DMEnd/p' "$login_file" | grep -v '^#|^*#|^$'
}
#==============================================================#
#                    配置文件数和进程数                         #
#==============================================================#
function conf_system_nproc() {
  log_print "配置文件数和进程数限制" ""
  local systemd_file="/etc/systemd/system.conf"
  # 在CentOS7 / RHEL7系统中使用Systemd替代了之前的SysV，需要修改/etc/systemd/system.conf文件。
  if [[ $os_version -ne 6 ]] && check_file "$systemd_file"; then
    # 备份文件
    bak_file "$systemd_file"
    # 修改/etc/systemd/system.conf文件
    params=(
      "# DMBegin"
      "DefaultLimitCORE=infinity"
      "DefaultLimitNOFILE=65536"
      "DefaultLimitNPROC=10240"
      "#DMEnd"
    )
    printf "%s\n" "${params[@]}" | tee -a "$systemd_file" >/dev/null
    sed -n '/DefaultLimit/p' "$systemd_file" | grep -v '^#'
  fi
  echo
  # 创建/etc/security/limits.d/dm-nproc.conf文件
  local nproc_file="/etc/security/limits.d/dm_nproc.conf"
  if ! check_file "$nproc_file"; then
    params=(
      "# DMBegin"
      "dmdba soft nproc 65536"
      "dmdba hard nproc 65536"
      "# DMEnd"
    )
    printf "%s\n" "${params[@]}" | tee -a "$nproc_file" >/dev/null
    sed -n '/# DMBegin/,/# DMEnd/p' "$nproc_file" | grep -v '^#|^*#|^$'
  else
    bak_file "$nproc_file"
    cat "$nproc_file"
  fi
}
#==============================================================#
#                    移除 IPC                                  #
#==============================================================#
# 此函数用于移除IPC服务
function conf_ipc() {
  if [[ $os_version -ne 6 ]]; then
    log_print "禁用 RemoveIPC" ""
    local logind_file="/etc/systemd/logind.conf"
    # 检查文件是否存在
    if ! check_file "$logind_file"; then
      color_printf red "${logind_file}文件不存在，无法进行修改操作。"
      return 1
    fi
    # 备份文件
    bak_file "$logind_file"
    # 修改文件
    params=(
      "# DMBegin"
      "RemoveIPC=no"
      "# DMEnd"
    )
    printf "%s\n" "${params[@]}" | tee -a "$logind_file" >/dev/null
    # 重新加载daemon和重启systemd-logind服务
    systemctl daemon-reload -q
    systemctl restart systemd-logind -q
    # 显示修改后的文件内容
    sed -n '/# DMBegin/,/# DMEnd/p' "$logind_file" | grep -v '^#|^*#|^$'
  fi
}
#==============================================================#
#                         配置主机名                           #
#==============================================================#
# 此函数用于修改数据库服务器主机名
function conf_hostname() {
  log_print "配置主机名" ""
  local network_file="/etc/sysconfig/network"
  # 配置dw和dsc主机名
  if [[ $dm_install_mode == "datawatch" || $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    for ((i = 1; i <= ${#wc_pubip_arr[@]} + ${#as_pubip_arr[@]}; i++)); do
      # 将主机名加入数组中
      if ((i < 10)); then
        wc_hostname_arr[i - 1]="${hostname}0$i"
      else
        wc_hostname_arr[i - 1]="${hostname}$i"
      fi
    done
    # 配置每个节点主机名
    hostname=${wc_hostname_arr[$((node_num - 1))]}
  fi
  # 获取当前主机名
  current_hostname=$(hostname)
  # 判断操作系统版本
  if [[ $os_version -eq 6 ]]; then
    # 获取当前主机名
    current_hostname=$(grep -E "HOSTNAME=" "${network_file}" | cut -d= -f2)
    bak_file "${network_file}"
    # 判断主机名是否需要修改
    if [[ $current_hostname != "$hostname" ]]; then
      # 修改主机名
      /bin/hostname "$hostname"
      sysctl kernel.hostname="$hostname"
      echo "$hostname" >/proc/sys/kernel/hostname
      sed -i "s/HOSTNAME=$current_hostname/HOSTNAME=$hostname/" "${network_file}"
    fi
    # 输出修改后的主机名
    grep -E "HOSTNAME=" "${network_file}" | cut -d= -f2
  else
    # 判断主机名是否需要修改
    if [[ $current_hostname != "$hostname" ]]; then
      # 修改主机名
      bak_file /etc/hostname
      hostnamectl set-hostname "$hostname"
    fi
    # 输出修改后的主机名
    hostnamectl
  fi
}
#==============================================================#
#                    配置 ntp timeserver                       #
#==============================================================#
# 定义函数：配置 ntp 服务
function conf_ntp() {
  # 定义事件任务计划
  local crontab_file="/var/spool/cron/root"
  local script_file="/usr/sbin/ntpdate"
  local ntp_file="/etc/ntp.conf"
  case $os_version in
  6 | 7)
    # 备份 ntp 配置文件和 sysconfig 配置文件
    bak_file "$ntp_file"
    bak_file /etc/sysconfig/ntpd
    # 删除 ntp 配置文件中已有的时间服务器信息，并添加新的时间服务器信息
    sed -i -e '/^server/d' -e '/^pool/d' "$ntp_file"
    cat <<-EOF >>"$ntp_file"
tos maxdist 30
# 将容忍时钟偏差修改至30，单位为S，该值默认为1.5. 当偏差大于此限定值时,ntp不会同步时钟。
tinker panic 0
# 当服务器时钟与本地时钟偏差大于1000时，ntp客户端会视为此偏差过大，不会同步时钟，且会退出ntpd服务，设置为0可忽略此限制。
server $timeserver_ip iburst
# 解决 strata too high 报错
server 127.127.1.0
fudge 127.127.1.0 stratum 8
# 解决 no data 报错
notrust
EOF
    # 创建 ntpd 进程ID文件
    touch /var/run/ntpd.pid
    # 编辑 sysconfig 文件来配置 ntpd 服务
    cat <<-EOF >/etc/sysconfig/ntpd
OPTIONS="-g -x -p /var/run/ntpd.pid"
SYNC_HWCLOCK=yes
EOF
    if ! crontab -l | grep -q "$script_file"; then
      echo "# DMBegin" >>"$crontab_file"
      echo "10 5 * * * root $script_file -u $timeserver_ip >/dev/null 2>&1 ; hwclock -w" >>"$crontab_file"
    fi
    ;;
  *)
    # 备份 chrony 配置文件
    bak_file /etc/chrony.conf
    # 删除 ntp 配置文件中已有的时间服务器信息，并添加新的时间服务器信息
    sed -i -e '/^server/d' -e '/^pool/d' /etc/chrony.conf
    cat <<-EOF >>/etc/chrony.conf
server $timeserver_ip iburst
allow $(ip route | grep "$(hostname -I)" | awk '{print $1}')
# not synchronized to any NTP server
local stratum 10
EOF
    if ! crontab -l | grep -q "$script_file"; then
      echo "# DMBegin" >>"$crontab_file"
      echo "10 5 * * * root $script_file -u $timeserver_ip >/dev/null 2>&1 ; hwclock -w" >>"$crontab_file"
    fi
    ;;
  esac
}
#==============================================================#
#                    配置时间同步                               #
#==============================================================#
# 定义函数：配置 时间同步 服务
function conf_timesync() {
  log_print "配置时间同步"
  packages=(
    ntp    #网络时间协议服务器同步时间
    chrony #Linux系统上提供时钟同步
  )
  install_packages_if_not_present "${packages[@]}"
  # 如果存在时间服务器 IP，则配置 NTP/chrony 服务
  if [[ $timeserver_ip ]]; then
    case $os_version in
    6)
      # 启动和开机启动 ntpd 服务
      service ntpd start
      chkconfig ntpd on
      # 配置 ntp
      conf_ntp
      # 手动同步一次服务器时间
      /usr/sbin/ntpdate -u "$timeserver_ip" >/dev/null 2>&1
      # 重启 ntpd 服务，并检查其状态及时间同步情况
      service ntpd restart
      # 记录时间同步后的日期和时间
      ntpstat
      echo
      ntpq -p -n
      ;;
    7)
      # 关闭并禁用 chrony 服务，备份 chrony 配置文件
      if systemctl is-active --quiet chronyd.service; then
        systemctl stop chronyd.service
        systemctl disable chronyd.service
        mv /etc/chrony.conf /etc/chrony.conf.bak
      fi
      # 启动和开机启动 ntpd 服务
      systemctl start ntpd.service
      systemctl enable ntpd.service
      # 配置 ntp
      conf_ntp
      # 手动同步一次服务器时间
      /usr/sbin/ntpdate -u "$timeserver_ip" >/dev/null 2>&1
      # 重启 ntpd 服务，并检查其状态及时间同步情况
      systemctl restart ntpd.service
      # 记录时间同步后的日期和时间
      ntpstat
      echo
      ntpq -p -n
      ;;
    *)
      # 关闭并禁用 ntpd 服务，备份 ntpd 配置文件
      if systemctl is-active --quiet ntpd.service; then
        systemctl stop ntpd.service
        systemctl disable ntpd.service
        mv /etc/ntpd.conf /etc/ntpd.conf.bak
      fi
      # 启动和开机启动 chronyd 服务
      systemctl start chronyd.service
      systemctl enable chronyd.service
      # 配置 ntp
      conf_ntp
      # 手动同步一次服务器时间
      /usr/sbin/ntpdate -u "$timeserver_ip" >/dev/null 2>&1
      # 重启 chrony 服务，并检查其状态及时间同步情况
      systemctl restart chronyd.service
      chronyc tracking
      echo
      chronyc sources -v
      ;;
    esac
  fi
}
#==============================================================#
#                    配置drop_cache脚本                         #
#==============================================================#
#此函数用于添加清除cache脚本
function conf_drop_cache() {
  # 配置缓存清除脚本，使用cron job在指定时间清除缓存
  log_print "配置cache脚本" "" # 打印日志，记录函数执行进度
  local crontab_file="/var/spool/cron/root"
  local script_file="/root/drop_cache.sh"
  # 备份现有的crontab文件
  bak_file "$crontab_file"
  # 创建缓存清除脚本
  cat <<-'EOF' >"$script_file"
#!/bin/bash
sync
echo 1 > /proc/sys/vm/drop_caches
EOF
  chmod 775 "$script_file"
  # 添加cron job到crontab文件
  if ! crontab -l | grep -q "$script_file"; then
    echo "* 6 * * * root $script_file" >>"$crontab_file"
  fi
  # 打印缓存清除脚本和当前的crontab配置
  cat "$script_file" && crontab -l
}
#==============================================================#
#                    配置profile                               #
#==============================================================#
# 此函数用于配置profile
function conf_profile() {
  log_print "配置/etc/profile" ""
  local locale_file="/etc/locale.conf"
  # 配置操作系统支持的所有字符集
  if [[ $os_version -ne 6 ]]; then
    bak_file "${locale_file}"
    if locale -a | grep -q "zh_CN.utf8"; then
      echo -e "LANG=zh_CN.utf8\nLC_CTYPE=zh_CN.utf8" >>"${locale_file}"
    else
      echo -e "LANG=en_US.utf8\nLC_CTYPE=en_US.utf8" >>"${locale_file}"
    fi
    grep <"${locale_file}" "zh_CN.utf8|en_US.utf8"
  fi
  # 修改文件
  # 导入语言
  local profile_file="/etc/profile"
  bak_file "$profile_file"
  if locale -a | grep -q "zh_CN.utf8"; then
    cat <<-'EOF' >>"$profile_file"
# DMBegin
export LANG=zh_CN.UTF-8
# DMEnd
EOF
  else
    cat <<-'EOF' >>"$profile_file"
# DMBegin
export LANG=en_US.UTF-8
# DMEnd
EOF
  fi
  awk '/export LANG/{print}' "$profile_file" | grep -v '^#|^*#|^$'
  echo
  # 内存池管理调整
  cat <<-'EOF' >>"$profile_file"
# DMBegin
export MALLOC_ARENA_MAX=4
# DMEnd
EOF
  # shellcheck source=/dev/null
  source "$profile_file"
  awk '/export MALLOC_ARENA_MAX/{print}' "$profile_file" | grep -v '^#|^*#|^$'
}
#==============================================================#
#                    配置用户环境变量                           #
#==============================================================#
# 此函数用于配置用户环境变量
function conf_user_profile() {
  log_print "配置用户环境变量" ""
  # 数据库实例路径
  local data_path=
  if [[ $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    data_path="$data_dir"/"$dsc_db_name"
  else
    data_path="$data_dir"/"$db_name"
  fi
  # 更新 root 用户的 bash 配置文件
  local bash_profile_file="/root/.bash_profile"
  bak_file "$bash_profile_file"
  cat <<-'EOF' >>$bash_profile_file
# DMBegin
alias sd='su - dmdba'
export PS1="[\u@\h:\w]\$ "
EOF
  # 更新 dmdba 用户的 bash 配置文件
  local bash_profile_file="/home/dmdba/.bash_profile"
  bak_file "$bash_profile_file"
  cat <<-EOF >>$bash_profile_file
# DMBegin
export DM_HOME="$env_dm_home"
export LD_LIBRARY_PATH="\$LD_LIBRARY_PATH:\$DM_HOME/bin"
export PATH="\$PATH:\$DM_HOME/bin:\$DM_HOME/tool"
export PS1="[\u@\h:\w]\$ "
alias dmbin="cd \$DM_HOME/bin"
alias dmlog="cd \$DM_HOME/log"
alias dmdata="cd $data_path"
alias ds="disql -L SYSDBA/'\"${sysdba_pwd//\$/\\\$}\"':$port_num"
alias dsql="disql -L -S SYSDBA/'\"${sysdba_pwd//\$/\\\$}\"':$port_num"
alias dssql="disql -L -S SYSDBA/'\"${sysdba_pwd//\$/\\\$}\"':$port_num"
EOF
  # 输出更新后的 dmdba 用户的 bash 配置文件内容
  grep <$bash_profile_file -E -v '^#|^$'
}
#==============================================================#
#                    挂载达梦安装镜像                           #
#==============================================================#
# 此函数用于挂载 DMISO 文件
function mount_iso() {
  log_print "挂载DMISO" ""
  # 创建或卸载 "${random_string}" 目录
  if [[ -d "${random_string}" ]]; then
    umount -f "${random_string}"
  else
    mkdir "${random_string}"
  fi
  # 创建或卸载 /run/media/root/dameng 目录
  if [[ -d /run/media/root/dameng ]]; then
    umount -f /run/media/root/dameng
  fi
  # 将 ISO 文件挂载到 "${random_string}" 目录
  mount -t auto -o ro,loop "${software_dir}/$dm_iso_name" "${random_string}"
  # 列出 "${random_string}" 目录下的内容
  ls -lrth "${random_string}"
}
#==============================================================#
#                    安装达梦数据库软件                          #
#==============================================================#
#此函数用于安装达梦数据库软件
function install_dmdb() {
  # 检查数据库是否已经安装
  if su - dmdba -c "$env_dm_home/bin/disql -id" >/dev/null 2>&1; then
    log_print "达梦数据库软件已经安装" ""
    su - dmdba -c "$env_dm_home/bin/disql -id"
    # 安装软件结束之后，卸载挂载
    umount -f "${random_string}" 2>&1
    # 删除生成的空目录
    find "${random_string}" -type d -empty -delete 2>&1
    return
  else
    log_print "开始安装达梦数据库软件" ""
  fi
  # 如果存在，备份文件
  bak_file "/etc/dm_svc.conf"
  # 生成安装配置文件 auto_install.xml
  local lang=zh
  if [[ $LANG = "zh_CN.UTF-8" ]]; then
    lang=zh
  elif [[ $LANG = "en_US.UTF-8" ]]; then
    lang=en
  fi
  cat <<-EOF >"${software_dir}"/auto_install.xml
<?xml version="1.0"?>
<DATABASE>
    <LANGUAGE>$lang</LANGUAGE>
    <TIME_ZONE>+08:00</TIME_ZONE>
    <KEY>$key_path</KEY>
    <INSTALL_TYPE>0</INSTALL_TYPE>
    <INSTALL_PATH>$env_dm_home</INSTALL_PATH>
    <INIT_DB>N</INIT_DB>
</DATABASE>
EOF
  # 恢复原环境中的/etc/dm_svc.conf文件
  restore_original_conf() {
    if check_file /etc/dm_svc.conf.original; then
      cp -pf /etc/dm_svc.conf.original /etc/dm_svc.conf # 将备份文件还原为原始文件
    fi
  }
  # 删除参数的目录
  cleanup() {
    # shellcheck disable=SC2317
    umount -f "${random_string}" 2>&1
    # shellcheck disable=SC2317
    find "${random_string}" -type d -empty -delete 2>&1
  }
  # 退出脚本时候，执行删除
  trap cleanup EXIT
  # 安装达梦数据库软件
  export DM_INSTALL_TMPDIR="${software_dir}"
  ulimit -n 65536
  # 安装数据库
  install_dm() {
    local install_bin="$1"
    local install_xml="$2"
    # 执行安装命令
    chmod -f +x "${install_bin}"
    "${install_bin}" -q "${install_xml}" 2>&1
    # 判断上一个命令是否执行成功，如果执行失败，退出脚本
    # shellcheck disable=SC2181
    if [ $? -ne 0 ]; then
      color_printf red "达梦数据库软件安装失败"
      restore_original_conf
      exit 1
    fi
  }
  # 使用不同的软件安装数据库
  if [[ -n "${dm_iso_name}" ]] || check_file "${random_string}/DMInstall.bin"; then
    install_dm "${random_string}/DMInstall.bin" "${software_dir}/auto_install.xml"
  elif check_file "${software_dir}/DMInstall.bin"; then
    install_dm "${software_dir}/DMInstall.bin" "${software_dir}/auto_install.xml"
  else
    color_printf red "找不到达梦数据库安装程序 DMInstall.bin"
    echo
    exit 1
  fi
  # 分布式模式需要添加动态链接库软链接
  if [[ $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    if [[ $(find /lib64/* -maxdepth 1 -name "libdm*.so" | wc -l) -le 6 ]]; then
      ln -s "$env_dm_home"/bin/*.so /lib64
    fi
  fi
  echo
}
#==============================================================#
#                       配置 glogin.sql                         #
#==============================================================#
function conf_glogin() {
  log_print "配置 glogin.sql"
  # 配置 glogin.sql
  bak_file "$env_dm_home"/bin/disql_conf/glogin.sql
  cat <<-'EOF' >"$env_dm_home"/bin/disql_conf/glogin.sql
-- 显示密钥过期时间
column expired_date new_value _edate
select to_char(expired_date,'yyyy-mm-dd') expired_date from v$license;
host echo "密钥过期时间：&_edate"
-- 设置dbms_output输出缓冲区大小
set serveroutput on size 1000000
-- 设置输出格式
set long 200
set linesize 500
set pagesize 5000
-- 去除重定向输出每行拖尾空格
set trimspool on
-- 去除行号显示
set lineshow off
-- 显示当前连接信息
col name new_value _dname
select name from v$database;
col port_num new_value _port
select para_value port_num from v$dm_ini where para_name='PORT_NUM';
set SQLPROMPT "_USER'@'_dname':'_port SQL> "
-- 显示当前时间
set time on
EOF
  grep -v "^\s*\(#\|$\|--\)" "$env_dm_home"/bin/disql_conf/glogin.sql
}
#==============================================================#
#                    初始化数据库实例                            #
#==============================================================#
function init_dmdb() {
  local dm_init_cmd="$env_dm_home/bin/dminit"
  # 如果数据库实例正在运行，停掉数据库实例
  if pgrep -f "$(upper "$db_name")/dm.ini" >/dev/null 2>&1; then
    log_print "停止数据库" ""
    su - dmdba -c "$DMS_STOP_CMD"
    mv "$data_dir/$(upper "$db_name")" "$data_dir/$(upper "$db_name")""$current" >/dev/null 2>&1
  fi
  log_print "初始化达梦数据库" ""
  # 检查参数是否有效
  if [[ -z "$data_dir" || -z "$db_name" || -z "$instance_name" || -z "$port_num" ]]; then
    color_printf red "错误：参数不能为空"
    return 1
  fi
  # 定义数据守护集群的instance_name
  if [ "$dm_install_mode" == "datawatch" ]; then
    if [ "$instance_name" == "DMSERVER" ]; then
      instance_name="${dw_group_name}_DW_0$node_num"
    else
      instance_name="${instance_name}0$node_num"
    fi
  fi
  # 执行 dminit 命令
  su - dmdba -c "$dm_init_cmd \
    PATH=$data_dir \
    EXTENT_SIZE=$extent_size \
    PAGE_SIZE=$page_size \
    CASE_SENSITIVE=$case_sensitive \
    CHARSET=$charset \
    LOG_SIZE=$log_size \
    DB_NAME=$db_name \
    INSTANCE_NAME=$instance_name \
    PORT_NUM=$port_num \
    BLANK_PAD_MODE=$blank_pad_mode"
  # shellcheck disable=SC2181
  if [ $? -ne 0 ]; then
    color_printf red "错误：执行 dminit 命令失败"
    return 1
  fi
}
#==============================================================#
#                    达梦数据库异步追踪日志                      #
#==============================================================#
#此函数用于配置SQLLOG日志
function dm_sqllog() {
  # 定义sqllog路径
  local db_dir
  # 定义sqllog.ini
  create_ini_file() {
    local db_dir="$1"
    su - dmdba <<-EOSQLLOG
    cat <<-EOF >"${db_dir}.ini"
BUF_TOTAL_SIZE          = 10240         #SQLs Log Buffer Total Size(K)(1024~1024000)
BUF_SIZE                = 1024          #SQLs Log Buffer Size(K)(50~409600)
BUF_KEEP_CNT            = 6             #SQLs Log buffer keeped count(1~100)
[SLOG_ALL]
FILE_PATH    = $db_dir
PART_STOR    = 0
SWITCH_MODE  = 2
SWITCH_LIMIT = 256
ASYNC_FLUSH  = 1
FILE_NUM = 100
ITEMS    = 0
SQL_TRACE_MASK  = 1
MIN_EXEC_TIME = 0
USER_MODE   = 0
USERS =
EXECTIME_PREC_FLAG = 0
[SLOG_ERROR]
SQL_TRACE_MASK = 23
FILE_PATH      = $db_dir
[SLOG_DDL]
SQL_TRACE_MASK = 3
[SLOG_LONG_SQL]
SQL_TRACE_MASK = 25
MIN_EXEC_TIME = 60000
EOF
EOSQLLOG
  }
  if [[ $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    # 如果安装模式为"dsc"
    for ((x = 0; x < ${#wc_hostip_arr[@]}; x++)); do
      # 针对每个IP地址，创建相应的SQL日志目录
      db_dir="$data_dir/$dsc_db_name/$(lower "$dsc_ep_name")${x}_config/sqllog"
      mkdir -p "$db_dir"                #创建目录及子目录
      chown -R dmdba:dinstall "$db_dir" # 给目录赋予适当的权限
      create_ini_file "$db_dir"
    done
  else
    #如果不是"dsc"安装模式，则只需创建一个SQL日志目录
    db_dir="$data_dir/$db_name/sqllog"
    mkdir -p "$db_dir"
    chown -R dmdba:dinstall "$db_dir"
    create_ini_file "$db_dir"
  fi
}
#==============================================================#
#                    注册实例服务                               #
#==============================================================#
#此函数用于注册数据库实例服务
function install_dmdb_svc() {
  log_print "注册数据库服务" ""
  readonly SERVICE_TYPE="dmserver"
  readonly CONFIG_FILE="$data_dir/$db_name/dm.ini"
  if [[ $os_version -ne 6 ]]; then
    "$env_dm_home"/script/root/dm_service_uninstaller.sh -n DmService"${db_name}" >/dev/null 2>&1 <<EOF
y
EOF
  fi
  "$env_dm_home"/script/root/dm_service_installer.sh -t "$SERVICE_TYPE" -dm_ini "$CONFIG_FILE" -p "$db_name"
}
#==============================================================#
#                    启停数据库                                 #
#==============================================================#
#此函数用于前台启动数据库
function start_dmdb() {
  readonly DM_SERVER_CMD="$env_dm_home/bin/dmserver"
  basic_dbinfo_sql=$(
    cat <<-'EOF'
set echo off
set timing off
set feedback off
set lineshow off
SELECT '实例名' 数据库参数项,
        instance_name 数据库参数值
  FROM v$instance
UNION ALL
SELECT svr_version,
        build_version "数据库版本"
  FROM v$instance
UNION ALL
SELECT '簇大小',CAST(sf_get_extent_size() AS VARCHAR)
UNION ALL
SELECT '页大小',CAST(page() / 1024 AS VARCHAR)
UNION ALL
SELECT '大小写敏感',CAST(sf_get_case_sensitive_flag() AS VARCHAR)
UNION ALL
SELECT '字符集',CAST(sf_get_unicode_flag() AS VARCHAR);
exit;
EOF
  )
  readonly basic_dbinfo_sql
  conf_dbinfo_file="${scripts_dir}/basic_dbinfo.sql"
  echo "$basic_dbinfo_sql" >"$conf_dbinfo_file"
  chown dmdba:dinstall "$conf_dbinfo_file"
  # 前台静默启停
  su - dmdba -c "$DM_SERVER_CMD $data_dir/$db_name/dm.ini <<-EOF
exit
EOF
" >/dev/null 2>&1
  echo
  # 启动数据库
  eval "su - dmdba -c \"$DMS_START_CMD\""
  echo
  # 查看数据库信息
  log_print "查询数据库基础参数信息" ""
  execute_script "basic_dbinfo.sql"
}
#==============================================================#
#                    数据守护主库脱机备份分发                    #
#==============================================================#
#此函数用于主库备份数据库
function dw_pri_backup() {
  log_print "数据守护主库脱机备份" ""
  color_printf blue "检查数据库进程是否存在："
  echo
  if pgrep -f "$data_dir/$db_name/dm.ini" >/dev/null; then
    color_printf yellow "数据库进程存在，尝试关闭主数据库："
    echo
    su - dmdba -c "$DMS_STOP_CMD"
    echo
    # 检查数据库是否已经停止
    while pgrep -f "$data_dir/$db_name/dm.ini" >/dev/null; do
      sleep 1
    done
  else
    color_printf green "数据库进程不存在，无需关闭"
    echo
  fi
  color_printf blue "主库进行脱机 dmrman 备份："
  echo
  su - dmdba -c "$env_dm_home/bin/dmrman use_ap=2 CTLSTMT=\"BACKUP DATABASE '$data_dir/$db_name/dm.ini' FULL TO BACKUP_FILE1 BACKUPSET '$backup_dir/$db_name/BACKUP_FILE_01'\""
  echo
  color_printf blue "主库查看脱机备份文件："
  echo
  ls -la "$backup_dir/$db_name"/BACKUP_FILE_01
}
#==============================================================#
#                    数据守护备机脱机恢复                        #
#==============================================================#
#此函数用于备库恢复数据库
function dw_std_restore() {
  log_print "数据守护备库脱机恢复" ""
  color_printf blue "检查数据库进程是否存在："
  echo
  if pgrep -f "$data_dir/$db_name/dm.ini" >/dev/null; then
    color_printf yellow "数据库进程存在，尝试关闭主数据库："
    echo
    su - dmdba -c "$DMS_STOP_CMD"
    echo
    # 检查数据库是否已经停止
    while pgrep -f "$data_dir/$db_name/dm.ini" >/dev/null; do
      sleep 1
    done
  else
    color_printf green "数据库进程不存在，无需关闭"
    echo
  fi
  color_printf blue "拷贝主库的备份文件并授权："
  # 获取节点一的备份文件
  scp -o StrictHostKeyChecking=no -q -P "$os_ssh_port" -r "${wc_hostip_arr[0]}":"$backup_dir/$db_name"/BACKUP_FILE_01 "$backup_dir/$db_name"
  chown -R dmdba:dinstall "$backup_dir"
  echo
  color_printf blue "备库执行脱机数据库 dmrman 还原："
  echo
  su - dmdba -c "$env_dm_home/bin/dmrman use_ap=2 CTLSTMT=\"RESTORE DATABASE '$data_dir/$db_name/dm.ini' FROM BACKUPSET '$backup_dir/$db_name/BACKUP_FILE_01'\""
  echo
  color_printf blue "备库执行 dmrman 数据库更新："
  echo
  su - dmdba -c "$env_dm_home/bin/dmrman use_ap=2 CTLSTMT=\"RECOVER DATABASE '$data_dir/$db_name/dm.ini' UPDATE DB_MAGIC\""
}
#==============================================================#
#                    修改数据库参数文件                         #
#==============================================================#
function dw_dmini() {
  # 主备库配置 dm.ini 参数
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库配置 dm.ini 参数："
  else
    color_printf blue "备库配置 dm.ini 参数："
  fi
  echo
  su - dmdba <<-DM
sed -i 's/ALTER_MODE_STATUS               = 0/ALTER_MODE_STATUS               = 1/g' $data_dir/$db_name/dm.ini
sed -i 's/ENABLE_OFFLINE_TS               = 1/ENABLE_OFFLINE_TS               = 2/g' $data_dir/$db_name/dm.ini
sed -i 's/MAL_INI                         = 0/MAL_INI                         = 1/g' $data_dir/$db_name/dm.ini
sed -i 's/ARCH_INI                        = 0/ARCH_INI                        = 1/g' $data_dir/$db_name/dm.ini
DM
  if [[ -n "$as_public_ip" && ($os_flag_value -eq 1 || $node_num -eq 1) ]]; then
    su - dmdba <<-DM
sed -i 's/TIMER_INI                       = 0/TIMER_INI                       = 1/g' $data_dir/$db_name/dm.ini
DM
  fi
  grep <"$data_dir"/"$db_name"/dm.ini "ALTER_MODE_STATUS" &&
    grep <"$data_dir"/"$db_name"/dm.ini "ENABLE_OFFLINE_TS" &&
    grep <"$data_dir"/"$db_name"/dm.ini "MAL_INI" &&
    grep <"$data_dir"/"$db_name"/dm.ini "ARCH_INI"
  # 打印异步备库参数
  if [[ -n "$as_public_ip" && ($os_flag_value -eq 1 || $node_num -eq 1) ]]; then
    grep <"$data_dir"/"$db_name"/dm.ini "TIMER_INI"
  fi
}
#==============================================================#
#                    修改数据库mal文件                         #
#==============================================================#
function dw_dmmal() {
  # 主备库配置 dmmal.ini 参数
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库配置 dmmal.ini 参数："
  else
    color_printf blue "备库配置 dmmal.ini 参数："
  fi
  echo
  # 写入头内容
  su - dmdba <<-DMMAL
  cat <<-EOF >$data_dir/$db_name/dmmal.ini
MAL_CHECK_INTERVAL = 15
MAL_CONN_FAIL_INTERVAL = 15
EOF
DMMAL
  # 数组循环输出
  for ((i = 0; i < ${#wc_pubip_arr[@]} + ${#as_pubip_arr[@]}; i++)); do
    su - dmdba <<-DMMAL
  cat <<-EOF >>$data_dir/$db_name/dmmal.ini
[MAL_INST$((i + 1))]
MAL_INST_NAME = ${instance_name%??}$(printf "%02d" $((i + 1)))
MAL_HOST = ${wc_malip_arr[$i]}
MAL_PORT = $mal_port_num
MAL_INST_HOST = ${wc_hostip_arr[i]}
MAL_INST_PORT = $port_num
MAL_DW_PORT = $mal_dw_port_num
MAL_INST_DW_PORT = $mal_inst_dw_port_num
EOF
DMMAL
  done
  cat "$data_dir"/"$db_name"/dmmal.ini
}
#==============================================================#
#                    修改数据库归档文件                         #
#==============================================================#
function dw_dmarch() {
  # 主备库配置 dmarch.ini 参数
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库配置 dmarch.ini 参数："
  else
    color_printf blue "备库配置 dmarch.ini 参数："
  fi
  echo
  # 写入本地归档内容
  su - dmdba <<-DMARCH
cat<<-EOF>>$data_dir/$db_name/dmarch.ini
ARCH_WAIT_APPLY = $arch_wait_apply
[ARCHIVE_LOCAL1]
ARCH_TYPE            = LOCAL
ARCH_DEST            = $arch_dir/$db_name
ARCH_FILE_SIZE       = $log_size
ARCH_SPACE_LIMIT     = $space_limit
EOF
DMARCH
  # 配置实[即]时主备归档
  if [[ $os_flag_value -eq 1 ]]; then
    # 数组循环输出
    for ((i = 0; i < ${#wc_pubip_arr[@]}; i++)); do
      if [[ $i -ne $((node_num - 1)) ]]; then
        arch_dest="${instance_name%??}$(printf "%02d" $((i + 1)))"
        su - dmdba <<-DMARCH
        cat<<-EOF>>$data_dir/$db_name/dmarch.ini
[ARCHIVE_${dw_arch_type}$((i + 1))]
ARCH_TYPE = $dw_arch_type
ARCH_DEST = $arch_dest
EOF
DMARCH
      fi
    done
  fi
  # 配置同[异]步主备归档
  if [[ $os_flag_value -eq 1 || $node_num -eq 1 ]]; then
    # 数组循环输出
    a=$((os_flag_value == 1 ? ${#as_pubip_arr[@]} : ${#as_pubip_arr[@]} - 1))
    b=$((os_flag_value == 1 ? ${#wc_pubip_arr[@]} : ${#wc_pubip_arr[@]} + 1))
    for ((i = 0; i < a; i++)); do
      arch_dest="${instance_name%??}$(printf "%02d" $((b + i + 1)))"
      su - dmdba <<-DMARCH
      cat<<-EOF>>$data_dir/$db_name/dmarch.ini
[ARCHIVE_ASYNC$((i + 1))]
ARCH_TYPE            = ASYNC
ARCH_DEST            = $arch_dest
ARCH_TIMER_NAME      = RT_TIMER$((i + 1))
ARCH_SEND_DELAY      = 0
EOF
DMARCH
    done
  fi
  cat "$data_dir"/"$db_name"/dmarch.ini
}
#==============================================================#
#                    修改数据库数据守护文件                     #
#==============================================================#
function dw_dmwatcher() {
  # 主备库配置 dmwatcher.ini 参数
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库配置 dmwatcher.ini 参数："
  else
    color_printf blue "备库配置 dmwatcher.ini 参数："
  fi
  echo
  su - dmdba <<-DMWATCH
cat<<-EOF>$data_dir/$db_name/dmwatcher.ini
[$dw_group_name]
DW_TYPE = GLOBAL
DW_MODE = $dw_mode
DW_ERROR_TIME = 10
INST_ERROR_TIME = 10
INST_OGUID = $oguid
INST_INI = $data_dir/$db_name/dm.ini
INST_AUTO_RESTART = 1
INST_STARTUP_CMD = $DMS_START_CMD
EOF
DMWATCH
  # 修改DW_TYPE类型
  if [[ -n "$as_public_ip" ]]; then
    # 判断什么时候需要修改DW_TYPE = GLOBAL值
    if [[ $os_flag_value -eq 0 ]]; then
      sed -i 's/DW_TYPE = GLOBAL/DW_TYPE = LOCAL/g' "$data_dir/$db_name/dmwatcher.ini"
      sed -i 's/DW_MODE = AUTO/DW_MODE = MANUAL/g' "$data_dir/$db_name/dmwatcher.ini"
    fi
  fi
  cat "$data_dir"/"$db_name"/dmwatcher.ini
  # 注册 dmwatcher 服务
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库注册 dmwatcher 服务："
  else
    color_printf blue "备库注册 dmwatcher 服务："
  fi
  echo
  if [[ $os_version -ne 6 ]]; then
    if check_file /usr/lib/systemd/system/DmWatcherService"$dw_group_name".service; then
      "$env_dm_home"/script/root/dm_service_uninstaller.sh -n DmWatcherService"$dw_group_name" >/dev/null 2>&1 <<EOF
y
EOF
    fi
  fi
  "$env_dm_home"/script/root/dm_service_installer.sh -t dmwatcher -watcher_ini "$data_dir"/"$db_name"/dmwatcher.ini -p "$dw_group_name"
}
#==============================================================#
#                    创建定时器                                #
#==============================================================#
function dw_timer() {
  if [[ -n "$as_public_ip" ]]; then
    # 备库配置监视器 dmtimer.ini 参数
    echo
    if [[ $node_num -eq 1 ]]; then
      color_printf blue "主库配置 dmtimer.ini 参数："
    else
      color_printf blue "备库配置 dmtimer.ini 参数："
    fi
    echo
    # 数组循环输出
    c=$((${#wc_pubip_arr[@]} ? ${#as_pubip_arr[@]} : ${#as_pubip_arr[@]} - 1))
    for ((i = 0; i < c; i++)); do
      su - dmdba <<-TIMER
    cat<<-EOF>>"$data_dir"/"$db_name"/dmtimer.ini
[RT_TIMER$((i + 1))]
TYPE = 10
FREQ_MONTH_WEEK_INTERVAL =
FREQ_SUB_INTERVAL =
#FREQ_MINUTE_INTERVAL = 1
REPEAT_INTERVAL= FREQ=SECONDLY;INTERVAL= 60
START_TIME = 01:00:00
END_TIME =
DURING_START_DATE = 2016-02-11 17:36:09
DURING_END_DATE = 9999-12-31 23:59:59
NO_END_DATE_FLAG = 1
DESCRIBE = RT_TIMER$((i + 1))
IS_VALID = 1
EOF
TIMER
    done
    chown dmdba:dinstall "$data_dir"/"$db_name"/dmtimer.ini
    cat "$data_dir"/"$db_name"/dmtimer.ini
  fi
}
#==============================================================#
#                    配置监视器                                  #
#==============================================================#
function dw_monitor() {
  if [[ $node_num -eq -1 ]]; then
    # 备库配置监视器 dmmonitor.ini 参数
    color_printf blue "监视器主机配置 dmmonitor.ini 参数："
    echo
    # 写入头内容
    su - dmdba <<-DMMON
cat<<-EOF>"$data_dir"/"$db_name"/dmmonitor_$dw_group_name.ini
MON_DW_CONFIRM = 1
MON_LOG_PATH = $data_dir/$db_name/log
MON_LOG_INTERVAL = 60
MON_LOG_FILE_SIZE = 32
MON_LOG_SPACE_LIMIT = 2048
[$dw_group_name]
MON_INST_OGUID = $oguid
EOF
DMMON
    # 数组循环输出
    for ((i = 0; i < ${#wc_pubip_arr[@]} + ${#as_pubip_arr[@]}; i++)); do
      su - dmdba <<-DMMON
cat<<-EOF>>"$data_dir"/"$db_name"/dmmonitor_$dw_group_name.ini
MON_DW_IP = ${wc_hostip_arr[$i]}:$mal_dw_port_num
EOF
DMMON
    done
    cat "$data_dir"/"$db_name"/dmmonitor_"$dw_group_name".ini
    # 注册 dmmonitor 服务
    echo
    color_printf blue "监视主机注册监视器服务："
    echo
    if [[ $os_version -ne 6 ]]; then
      if check_file /usr/lib/systemd/system/DmMonitorService"${dw_group_name}".service; then
        "$env_dm_home"/script/root/dm_service_uninstaller.sh -n DmMonitorService"${dw_group_name}" >/dev/null 2>&1 <<EOF
y
EOF
      fi
    fi
    "$env_dm_home"/script/root/dm_service_installer.sh -t dmmonitor -monitor_ini "$data_dir"/"$db_name"/dmmonitor_"$dw_group_name".ini -p "$dw_group_name"
    # 启动监视进程
    echo
    color_printf blue "监视主机启动监视进程："
    echo
    su - dmdba -c "$DMM_START_CMD"
  else
    echo
    if [[ $node_num -eq 1 ]]; then
      color_printf blue "主库配置 dmmonitor.ini 参数："
    else
      color_printf blue "备库配置 dmmonitor.ini 参数："
    fi
    echo
    # 写入头内容
    su - dmdba <<-DMMON
cat<<-EOF>$data_dir/$db_name/dmmonitor_$dw_group_name.ini
MON_DW_CONFIRM = 0
MON_LOG_PATH = $env_dm_home/log
MON_LOG_INTERVAL = 60
MON_LOG_FILE_SIZE = 32
MON_LOG_SPACE_LIMIT = 2048
[$dw_group_name]
MON_INST_OGUID = $oguid
EOF
DMMON
    # 数组循环输出
    for ((i = 0; i < ${#wc_pubip_arr[@]} + ${#as_pubip_arr[@]}; i++)); do
      su - dmdba <<-DMMON
cat<<-EOF>>$data_dir/$db_name/dmmonitor_$dw_group_name.ini
MON_DW_IP = ${wc_malip_arr[$i]}:$mal_dw_port_num
EOF
DMMON
    done
    cat "$data_dir"/"$db_name"/dmmonitor_"$dw_group_name".ini
  fi
}
#==============================================================#
#                    数据守护主备配置                          #
#==============================================================#
function dw_conf_para() {
  log_print "数据守护启动" ""
  # 主备库启动到 mount 状态
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库启动到 mount 状态："
  else
    color_printf blue "备库启动到 mount 状态："
  fi
  echo
  su - dmdba -c "$DMS_START_CMD mount"
  # 修改主备库模式
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库修改模式："
  else
    color_printf blue "备库修改模式："
  fi
  echo
  if [[ $node_num -eq 1 ]]; then
    cat <<-EOF >${scripts_dir}/dw_conf_pri.sql
set echo off
set timing off
set feedback off
set lineshow off
sp_set_oguid($oguid);
alter database primary;
SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);
exit;
EOF
    chown dmdba:dinstall ${scripts_dir}/dw_conf_pri.sql
    execute_script "dw_conf_pri.sql"
  else
    cat <<-EOF >${scripts_dir}/dw_conf_std.sql
set echo off
set timing off
set feedback off
set lineshow off
sp_set_oguid($oguid);
alter database standby;
SP_SET_PARA_VALUE(1, 'ALTER_MODE_STATUS', 0);
exit;
EOF
    chown dmdba:dinstall ${scripts_dir}/dw_conf_std.sql
    execute_script "dw_conf_std.sql"
  fi
  # 启动守护进程
  echo
  if [[ $node_num -eq 1 ]]; then
    color_printf blue "主库启动守护进程："
  else
    color_printf blue "备库启动守护进程："
  fi
  echo
  su - dmdba -c "$DMW_START_CMD"
  if [[ $node_num -eq 1 ]]; then
    # 执行备份脚本，备份数据库
    log_print "测试作业备份数据库" ""
    execute_script "check_backup.sql"
    log_print "创建DMDBA用户，密码：$sysdba_pwd" ""
    execute_script "create_user.sql"
    log_print "查询数据库优化结果：" ""
    su - dmdba -c "disql -S SYSDBA/'\"$sysdba_pwd\"':$port_num \\\`${scripts_dir}/query_dmini.sql"
  fi
}
#==============================================================#
#                    DSC部署                                   #
#==============================================================#
# 创作不易，且行且珍惜，2022年进步劳动成果之一
#==============================================================#
#                    配置ASM磁盘                                #
#==============================================================#
function dsc_conf_asmdisk() {
  # 定义参数变量
  local device_name="$1"
  local symlink_name="$2"
  # get_uuid_or_dm_uuid
  get_uuid_or_dm_uuid() {
    local device_path=$1 # 设备路径
    local uuid
    if [[ $multipath == "Y" ]]; then # 如果启用了多路径，并且设备路径包含dev字符串
      if [[ $device_path =~ "/dev" ]]; then
        uuid="mpath-"$(get_uuid "$device_path") # 获取设备的普通UUID
      else
        uuid=$(get_dm_uuid "$device_path") # 获取设备的多路径UUID
      fi
    else
      uuid=$(get_uuid "$device_path") # 获取设备的普通UUID
    fi
    echo "$uuid"
  }
  # 根据变量 multipath 的值设置规则文件名
  local rules_file=""
  if [[ $multipath = "Y" ]]; then
    rules_file="$UDEV_RULES_PATH/$DM_PERMISSIONS_RULES"
  else
    rules_file="$UDEV_RULES_PATH/$DM_ASMDEVICES_RULES"
  fi
  # 定义内嵌函数
  # 创建 multipath 规则
  create_multipath_rule() {
    local uuid
    uuid=$(get_uuid "$1")
    cat <<EOF >>$MULTIPATH_CONF_PATH
  multipath {
    wwid   "$uuid"
    alias  $2
  }
EOF
  }
  # 创建符号链接规则
  create_symlink_rule() {
    echo "KERNEL==\"dm-*\", ENV{DM_UUID}==\"$(get_uuid_or_dm_uuid "$device_name")\", SYMLINK+=\"asmdisk/$symlink_name\", OWNER=\"dmdba\", GROUP=\"dinstall\", MODE=\"0660\"" >>$rules_file
  }
  # 创建 SCSI ID 规则
  create_scsi_id_rule() {
    echo "KERNEL==\"sd*\", BUS==\"scsi\", PROGRAM==\"/sbin/scsi_id -g -u -d /dev/\$name\", RESULT==\"$(get_uuid_or_dm_uuid "$device_name")\", SYMLINK+=\"asmdisk/$symlink_name\", OWNER=\"dmdba\", GROUP=\"dinstall\", MODE=\"0660\"" >>$rules_file
  }
  # 添加设备符号链接
  creeate_device_symlink() {
    echo "KERNEL==\"sd*\", ENV{ID_PART_ENTRY_UUID}==\"$(get_uuid_or_dm_uuid "$device_name")\", SYMLINK+=\"asmdisk/$symlink_name\", OWNER=\"dmdba\", GROUP=\"dinstall\", MODE=\"0660\"" >>$rules_file
  }
  # 创建块设备规则
  create_block_rule() {
    echo "KERNEL==\"sd*\", SUBSYSTEM==\"block\", PROGRAM==\"/usr/lib/udev/scsi_id -g -u -d /dev/\$name\", RESULT==\"$(get_uuid_or_dm_uuid "$device_name")\", SYMLINK+=\"asmdisk/$symlink_name\", OWNER=\"dmdba\", GROUP=\"dinstall\", MODE=\"0660\"" >>$rules_file
  }
  # 创建udev规则文件
  if [[ $multipath = "Y" ]]; then
    if [[ $1 =~ "dev" ]]; then
      # 调用内嵌函数创建 multipath 规则和符号链接规则
      create_multipath_rule "$device_name" "$symlink_name"
    fi
    # 检查是否存在属于 dmdba 或 dinstall 的现有设备
    if find /dev/ -maxdepth 1 \( -user "dmdba" -or -group "dinstall" \) >/dev/null 2>&1; then
      create_symlink_rule
      return
      # 检查 /dev/asmdisk 是否存在以 "dm" 开头的文件
    elif [[ ! -d $ASM_DEVICE_PATH || $(find $ASM_DEVICE_PATH -mindepth 1 -name 'dm*' | wc -l) -eq 0 ]]; then
      create_symlink_rule
      return
    fi
  else
    # 根据操作系统版本和可用信息创建 udev 规则
    if [[ $os_version -eq 6 ]]; then
      create_scsi_id_rule
    else
      if [[ $device_name =~ [0-9] ]]; then
        creeate_device_symlink
      else
        create_block_rule
      fi
    fi
  fi
}
#==============================================================#
#                    配置udev生效                               #
#==============================================================#
function dsc_conf_udev() {
  if [[ $multipath = "Y" && ${disk_arr[0]} =~ "dev" ]]; then
    log_print "查看multipath聚合磁盘" "" # 打印查看multipath聚合磁盘的信息
    # 配置多路径开机自启
    case "$os_version" in
    "6")
      chkconfig multipathd.service on
      ;;
    *)
      systemctl restart multipathd -q
      systemctl enable multipathd -q
      ;;
    esac
    multipath -F
    multipath -r >/dev/null 2>&1
    multipath -ll
  fi
  # 启动udev
  while true; do
    if [[ $os_version -eq 6 ]]; then
      echo
      start_udev # 在CentOS 6上启动udev服务
    else
      /sbin/udevadm control --reload-rules                 # 重新加载udev规则
      /sbin/udevadm trigger --type=devices --action=change # 触发设备变化事件
    fi
    sleep 5s
    if [[ $(find $ASM_DEVICE_PATH -mindepth 1 -name 'dm*' | wc -l) -gt 0 ]]; then
      log_print "查看udev磁盘" "" # 打印查看udev磁盘的信息
      ls -lh $ASM_DEVICE_PATH
      break
    fi
  done
}
#==============================================================#
#                    创建ASM磁盘数组                            #
#==============================================================#
function dsc_conf_disk_arr() {
  if [[ $node_num -gt 1 ]]; then
    log_print "拷贝UDEV规则文件，并启动UDEV" ""
  else
    log_print "创建UDEV规则文件，并启动UDEV" ""
  fi
  # 根据变量 multipath 的值设置规则文件名
  # 规则文件路径
  local rules_file=""
  if [[ $multipath = "Y" ]]; then
    rules_file="$UDEV_RULES_PATH/$DM_PERMISSIONS_RULES"
  else
    rules_file="$UDEV_RULES_PATH/$DM_ASMDEVICES_RULES"
  fi
  # 备份规则文件，并删除现有的规则文件
  bak_file "rules_file" && rm -f "$rules_file"
  if [[ ${disk_arr[0]:0:4} == "/dev" ]]; then
    # 标记磁盘为/dev类型
    local is_dev_disk=true
  else
    local is_dev_disk=false
  fi
  if [[ $multipath = "Y" && $is_dev_disk = true ]]; then
    # 如果磁盘是/dev类型且需要使用多路径，则标记磁盘为多路径类型
    local is_multipath=true
  else
    local is_multipath=false
  fi
  # 安装multipath软件
  if $is_multipath; then
    # 安装device-mapper-multipath软件包
    packages=("device-mapper-multipath")
    install_packages_if_not_present "${packages[@]}"
    # 生成multpah.conf配置文件
    mpathconf --enable --with_multipathd y
  fi
  DEFAULT_MULTIPATH_CONF=$(
    cat <<'EOF'
defaults {
  polling_interval        30
  failback                immediate
  no_path_retry           5
  rr_min_io               100
  path_checker            tur
  user_friendly_names     yes
}
EOF
  )
  # 生成multipath.conf文件
  if [[ "$node_num" -eq 1 && "$is_multipath" = true ]]; then
    cat <<EOF >"$MULTIPATH_CONF_PATH"
$DEFAULT_MULTIPATH_CONF

blacklist {
  devnode "^(ram|raw|loop|fd|md|dm-|sr|scd|st)[0-9]*"
  devnode "^(hd|xvd|vd)[a-z]*"
}

multipaths {
EOF
  fi
  # 生成multipath文件关于磁盘uuid的内容
  my_dsc_conf_asmdisk() {
    for ((x = 0; x < ${#disk_arr[@]}; x++)); do
      y=1
      for i in ${disk_arr[$x]//,/ }; do
        if ((y < 10)); then
          dsc_conf_asmdisk "$(lower "$i")" "${asm_disk_arr[$x]}"0$y
        else
          dsc_conf_asmdisk "$(lower "$i")" "${asm_disk_arr[$x]}"$y
        fi
        y=$((y + 1))
      done
    done
  }
  # 打印multipath.conf和udev.rules规则文件
  if [ "$node_num" -eq 1 ]; then
    if $is_multipath; then
      my_dsc_conf_asmdisk                                                                                                                       # 配置asmdisk
      printf "}\n\ndevices {\n  device {\n    path_grouping_policy  group_by_prio\n    prio alua  #failover\n  }\n}\n" >>"$MULTIPATH_CONF_PATH" # 在multipath配置文件中添加路径分组策略和优先级
      printf "\e[34m文件 %s 的内容为：\e[0m\n\n%s\n\n" "$MULTIPATH_CONF_PATH" "$(cat $MULTIPATH_CONF_PATH)"                                            # 打印multipath配置文件的内容
    else
      my_dsc_conf_asmdisk # 配置asmdisk
    fi
    printf "\e[34m文件 %s 的内容为：\e[0m\n\n%s\n" "$rules_file" "$(cat $rules_file)"
  else
    if $is_multipath; then
      exec_scp_cmd "$MULTIPATH_CONF_PATH" "${wc_hostip_arr[0]}" "$MULTIPATH_CONF_PATH" "s"
      # 从第一个节点复制multipath配置文件到本地
      printf "\e[34m文件 %s 的内容为：\e[0m\n\n%s\n\n" "$MULTIPATH_CONF_PATH" "$(cat $MULTIPATH_CONF_PATH)" # 打印multipath配置文件的内容
      # 检查是否存在属于 dmdba 或 dinstall 的现有设备
      if find /dev/ -maxdepth 1 \( -user "dmdba" -or -group "dinstall" \) >/dev/null 2>&1; then
        exec_scp_cmd "$rules_file" "${wc_hostip_arr[0]}" "$rules_file" "s"
      elif [[ ! -d $ASM_DEVICE_PATH || $(find $ASM_DEVICE_PATH -mindepth 1 -name 'dm*' | wc -l) -eq 0 ]]; then
        exec_scp_cmd "$rules_file" "${wc_hostip_arr[0]}" "$rules_file" "s"
      fi
    else
      exec_scp_cmd "$rules_file" "${wc_hostip_arr[0]}" "$rules_file" "s"
    fi
    printf "\e[34m文件 %s 的内容为：\e[0m\n\n%s\n" "$rules_file" "$(cat $rules_file)" # 打印udev规则文件的内容
  fi
  dsc_conf_udev
}
#==============================================================#
#                    配置dmdcr_cfg.ini                         #
#==============================================================#
function dsc_dmdcr_cfg() {
  local cfg_path="$data_dir/$dsc_db_name/dmdcr_cfg.ini"
  local ep_count=${#wc_malip_arr[@]}
  write_cfg_endpoints() {
    if [[ $dm_install_mode == "dsc" ]]; then
      printf "%s\n" "DCR_N_GRP = 3" \
        "DCR_VTD_PATH = $ASM_DEVICE_PATH/${asm_disk_arr[1]}01" \
        "DCR_OGUID = $oguid" >"$cfg_path"
    else
      printf "%s\n" "DCR_N_GRP = 3" \
        "DCR_DISK_LOAD_PATH = $ASM_DEVICE_PATH" \
        "DCR_OGUID = $oguid" >"$cfg_path"
    fi
  }
  # 存储节点配置信息的关联数组
  declare -A groups=(
    ["CSS"]="DCR_GRP_TYPE = CSS
DCR_GRP_NAME = GRP_CSS
DCR_GRP_N_EP = $ep_count
DCR_GRP_DSKCHK_CNT = 60"
    ["ASM"]="DCR_GRP_TYPE = ASM
DCR_GRP_NAME = GRP_ASM
DCR_GRP_N_EP = $ep_count
DCR_GRP_DSKCHK_CNT = 60"
    ["DB"]="DCR_GRP_TYPE = DB
DCR_GRP_NAME = GRP_$dsc_db_name
DCR_GRP_N_EP = $ep_count
DCR_GRP_DSKCHK_CNT = 60"
  )
  # 内嵌函数：写入CSS节点的端点信息
  write_css_endpoints() {
    # 循环遍历CSS节点并写入文件
    for ((x = 0; x < ${#wc_malip_arr[@]}; x++)); do
      cat <<-CONFIG >>"$cfg_path"
[GRP_CSS]
DCR_EP_NAME = CSS$x
DCR_EP_HOST = ${wc_malip_arr[$x]}
DCR_EP_PORT = $css_dcr_ep_port
CONFIG
    done
  }
  # 内嵌函数：写入ASM节点的端点信息
  write_asm_endpoints() {
    # 循环遍历ASM节点并写入文件
    for ((x = 0; x < ${#wc_malip_arr[@]}; x++)); do
      cat <<-CONFIG >>"$cfg_path"
[GRP_ASM]
DCR_EP_NAME = ASM$x
DCR_EP_SHM_KEY = 9336$x
DCR_EP_SHM_SIZE = 512
DCR_EP_HOST = ${wc_malip_arr[$x]}
DCR_EP_PORT = $asm_dcr_ep_port
CONFIG
      if [[ $dm_install_mode == "dsc" ]]; then
        printf "%s\n" "DCR_EP_ASM_LOAD_PATH = $ASM_DEVICE_PATH" >>"$cfg_path"
      fi
    done
  }
  # 内嵌函数：写入DB节点的端点信息
  write_db_endpoints() {
    # 循环遍历DB节点并写入文件
    for ((x = 0; x < ${#wc_malip_arr[@]}; x++)); do
      cat <<-CONFIG >>"$cfg_path"
[GRP_$dsc_db_name]
DCR_EP_NAME = $(upper "${dsc_ep_name}")$x
DCR_EP_SEQNO = $x
DCR_EP_PORT = $port_num
DCR_CHECK_PORT = $db_dcr_ep_port
CONFIG
    done
  }
  # 内嵌函数：创建新的配置文件
  create_config_file() {
    # 配置dmdcr_cfg.ini参数
    write_cfg_endpoints
    # 写入端点配置
    # 按照 CSS ASM DB 顺序遍历每个组并写入文件
    for group in CSS ASM DB; do
      # 如果当前组存在，则写入组信息和端点信息
      if [[ "${groups[$group]+exists}" ]]; then
        cat <<-CONFIG >>"$cfg_path"
[GRP]
${groups[$group]}
CONFIG
        # 根据当前组写入端点信息
        case "$group" in
        "CSS") write_css_endpoints ;; # 写入 CSS 组的端点信息
        "ASM") write_asm_endpoints ;; # 写入 ASM 组的端点信息
        "DB") write_db_endpoints ;;   # 写入 DB 组的端点信息
        esac
      fi
    done
  }
  # 内嵌函数：复制现有的配置文件
  copy_config_file() {
    log_print "正在复制用于DSC的参数文件 $dsc_db_name" ""
    # 复制文件到本地目录并更改所有权
    exec_scp_cmd "$cfg_path" "${wc_hostip_arr[0]}" "$cfg_path" "s"
  }
  # 主逻辑
  if [[ $node_num -eq 1 ]]; then
    log_print "正在创建用于DSC的参数文件 $dsc_db_name" ""
    # 创建新的配置文件
    create_config_file
  else
    # 复制现有的配置文件
    copy_config_file
  fi
  chown -R dmdba:dinstall "$cfg_path"
}
#==============================================================#
#                    配置dmasmvrmal.ini                        #
#==============================================================#
function dsc_dmasvmal() {
  local dmasvmal_path="$data_dir/$dsc_db_name/dmasvrmal.ini"
  # 检查dmasvrmal.ini文件是否存在，如果存在则删除
  if check_file "$dmasvmal_path"; then
    rm -f "$dmasvmal_path"
  fi
  # 如果节点是集群中的第一个节点
  if [[ $node_num -eq 1 ]]; then
    # 循环遍历wc_malip_arr数组，并为每个元素创建一个[MAL_INST]部分
    for ((x = 0; x < ${#wc_malip_arr[@]}; x++)); do
      # 将[MAL_INST]部分追加到dmasvrmal.ini文件中
      cat <<-EOF >>"$dmasvmal_path"
[MAL_INST$((x + 1))]
   MAL_INST_NAME              = ASM$x
   MAL_HOST                   = ${wc_malip_arr[$x]}
   MAL_PORT                   = $asvrmal_mal_port

EOF
    done
  # 如果节点不是集群中的第一个节点
  else
    # 将dmasvrmal.ini文件从第一个节点的数据目录复制到当前节点的数据目录
    exec_scp_cmd "$dmasvmal_path" "${wc_hostip_arr[0]}" "$dmasvmal_path" "s"
  fi
  # 将dmasvrmal.ini文件的所有权更改为dmdba:dinstall
  chown dmdba:dinstall "$dmasvmal_path"
}
#==============================================================#
#                    配置dmdcr.ini                             #
#==============================================================#
#参数DMDCR_SEQNO不同节点值不一样，从0开始依次增加
function dsc_dmdcr() {
  local dmdcr_path="$data_dir/$dsc_db_name/dmdcr.ini"
  # 定义DCR_PATH路径
  if [[ $dm_install_mode == "dsc" ]]; then
    printf "DMDCR_PATH                 = %s\n" "$ASM_DEVICE_PATH/${asm_disk_arr[0]}01" >"$dmdcr_path"
  else
    printf "DMDCR_PATH                 = %s\n" "$ASM_DEVICE_PATH" >"$dmdcr_path"
  fi
  # 补充完整配置文件
  cat <<-EOF >>"$dmdcr_path"
DMDCR_MAL_PATH             = $data_dir/$dsc_db_name/dmasvrmal.ini
DMDCR_SEQNO                = $((node_num - 1))
DMDCR_ASM_RESTART_INTERVAL = 0
DMDCR_ASM_STARTUP_CMD      = $ASMSVR_START_CMD
DMDCR_DB_RESTART_INTERVAL  = 0
DMDCR_DB_STARTUP_CMD       = $DB_START_CMD
DMDCR_AUTO_OPEN_CHECK      = 111
DMDCR_ASM_TRACE_LEVEL      = 2
EOF
  # 添加 ping 权限
  if [[ -n "$link_check_ip" ]]; then
    printf "DMDCR_LINK_CHECK_IP        = %s\n" "$link_check_ip" >>"$dmdcr_path"
    # 将文件名提取为数组
    files=("dmasmsvr" "dmasmsvrm" "dmserver")
    # 根据不同的模式设置需要跳过的文件名字符串
    case $dm_install_mode in
    "dsc")
      skip_files="dmasmsvrm"
      ;;
    "dscm")
      skip_files="dmasmsvr"
      ;;
    *)
      skip_files=""
      ;;
    esac
    # 赋值ping
    for fs in "${files[@]}"; do
      # 如果文件名在需要跳过的字符串中，则跳过
      if [[ "$skip_files" == "$fs" ]]; then
        continue
      fi
      setcap cap_net_raw,cap_net_admin=eip "$env_dm_home/bin/$fs"
    done
  fi
  chown dmdba:dinstall "$dmdcr_path"
}
#==============================================================#
#                    DSC集群监视器                              #
#==============================================================#
# 生成dmcssm.ini配置文件
function dsc_dmcssm() {
  local dmcssm_path="$data_dir/$dsc_db_name/dmcssm.ini"
  cat <<-EOF >"$dmcssm_path"
CSSM_OGUID = $oguid
$(echo "${wc_malip_arr[@]}" | awk -v port="$css_dcr_ep_port" '{for(i=1;i<=NF;i++) printf "CSSM_CSS_IP = %s:%s\n", $i, port}')
CSSM_LOG_PATH = $env_dm_home/log
CSSM_LOG_FILE_SIZE = 32
CSSM_LOG_SPACE_LIMIT = 0
EOF
  chown dmdba:dinstall "$dmcssm_path"
  ls -lh "$data_dir/$dsc_db_name"
}
#==============================================================#
#                    数据库连接服务名                           #
#==============================================================#
function dsc_dmsvc() {
  # 使用 Here Document 将配置设置追加到 /etc/dm_svc.conf 文件中
  local dm_svc_file="/etc/dm_svc.conf"
  cat <<-EOF >>"$dm_svc_file"
[$dsc_db_name]
$dsc_db_name=($(printf "%s:$port_num," "${wc_hostip_arr[@]}" | sed 's/,$//'))
SWITCH_TIMES=(60)
SWITCH_INTERVAL=(1000)
EP_SELECTOR=(0)
AUTO_RECONNECT=(1)
EOF
}
#==============================================================#
#                    初始化磁盘                                 #
#==============================================================#
function dsc_dmasmcmd() {
  log_print "初始化 ASM 磁盘" ""
  # 初始化 ASM 磁盘路径变量
  local asm_disk_path=""
  # 初始化 ASM 磁盘路径集合变量
  local asm_disk_paths=""
  # 设置 ASM 磁盘配置文件路径
  local asm_disk_file="$data_dir/$dsc_db_name/asmdisk.txt"
  # 删除已存在的 ASM 磁盘配置文件
  rm -f "$asm_disk_file"
  # 写入 ASM 脚本文件的头部注释信息
  printf "#asm script file\n" >"$asm_disk_file"
  for ((i = 0; i < ${#disk_arr[@]}; i++)); do
    IFS=',' read -ra disk_arr_temp <<<"${disk_arr[$i]}"
    for ((j = 1; j <= ${#disk_arr_temp[@]}; j++)); do
      asm_disk_name=${asm_disk_arr[$i]}$(printf '%02d' $j)
      asm_disk_path="$ASM_DEVICE_PATH/$asm_disk_name"
      # 将创建 ASM 磁盘的命令写入配置文件
      if [[ $dm_install_mode == "dsc" ]]; then
        case $i in
        0)
          # 创建dcr磁盘
          printf "create %s '%s' '%s'\n" "dcrdisk" "$asm_disk_path" "$asm_disk_name" >>"$asm_disk_file"
          ;;
        1)
          # 创建vote磁盘
          printf "create %s '%s' '%s'\n" "votedisk" "$asm_disk_path" "$asm_disk_name" >>"$asm_disk_file"
          ;;
        *)
          # 创建asm磁盘
          printf "create %s '%s' '%s'\n" "asmdisk" "$asm_disk_path" "$asm_disk_name" >>"$asm_disk_file"
          ;;
        esac
      else
        printf "create %s '%s' '%s'\n" "$([ $i == 0 ] && echo 'dcrvdisk' || echo 'asmdisk')" "$asm_disk_path" "$asm_disk_name" >>"$asm_disk_file"
        # 如果是第一个磁盘
        if [[ $i == 0 ]]; then
          # 将磁盘路径添加到路径集合中
          asm_disk_paths+="'$asm_disk_path',"
        fi
      fi
    done
  done
  # 将创建 ASM 磁盘的命令写入配置文件
  if [[ $dm_install_mode == "dsc" ]]; then
    # 初始化dcr和vote磁盘
    printf "init dcrdisk '$ASM_DEVICE_PATH/%s' from '$data_dir/$dsc_db_name/dmdcr_cfg.ini' identified by '$asm_pwd'\n" "${asm_disk_arr[0]}01" >>"$asm_disk_file"
    printf "init votedisk '$ASM_DEVICE_PATH/%s' from '$data_dir/$dsc_db_name/dmdcr_cfg.ini'\n" "${asm_disk_arr[1]}01" >>"$asm_disk_file"
    # 以 dmdba 用户身份运行 asm 磁盘创建脚本
    chown dmdba:dinstall "$asm_disk_file"
    su - dmdba -c "$env_dm_home/bin/dmasmcmd script_file=$asm_disk_file"
  else
    # 去除路径集合变量末尾的逗号
    asm_disk_paths="${asm_disk_paths%,}"
    # 将创建 ASM 磁盘组的命令写入配置文件
    printf "\ncreate system diskgroup asmdisk %s attribute config='$data_dir/$dsc_db_name/dmdcr_cfg.ini', passwd='$asm_pwd'\n" "$asm_disk_paths" >>"$asm_disk_file"
    # 以 dmdba 用户身份运行 asm 磁盘创建脚本
    chown dmdba:dinstall "$asm_disk_file"
    su - dmdba -c "$env_dm_home/bin/dmasmcmdm script_file=$asm_disk_file"
  fi
}
#==============================================================#
#                    注册服务                                   #
#==============================================================#
function dsc_dmservice() {
  log_print "注册css和asm服务" ""
  # 删除残余服务脚本
  if [[ $os_version -ne 6 ]]; then
    "$env_dm_home"/script/root/dm_service_uninstaller.sh -n "$DM_CSS_SERVICE" >/dev/null 2>&1 <<EOF
y
EOF
    "$env_dm_home"/script/root/dm_service_uninstaller.sh -n "$DM_ASM_SERVICE" >/dev/null 2>&1 <<EOF
y
EOF
  fi
  # 安装 CSS 和 ASMSVR 服务
  local DM_SERVICE_INSTALLER="$env_dm_home/script/root/dm_service_installer.sh"
  local DCR_INI="$data_dir/$dsc_db_name/dmdcr.ini"
  $DM_SERVICE_INSTALLER -t dmcss -dcr_ini "$DCR_INI" -p Css
  echo
  if [[ $dm_install_mode == "dsc" ]]; then
    $DM_SERVICE_INSTALLER -t dmasmsvr -dcr_ini "$DCR_INI" -p Asmsvr -y $DM_CSS_SERVICE
  else
    $DM_SERVICE_INSTALLER -t dmasmsvrm -dcr_ini "$DCR_INI" -p Asmsvr -y $DM_CSS_SERVICE
  fi
}
#==============================================================#
#                     循环启动所有的节点的css和asm实例的服务      #
#==============================================================#
function dsc_start_ca() {
  log_print "启动css和asm服务" ""
  local WAIT_TIME="5s" # 等待时间
  #依次启动每个节点的css服务
  for ((x = 0; x < ${#wc_hostip_arr[@]}; x++)); do
    while true; do
      sleep "${WAIT_TIME}"
      exec_ssh_cmd "${wc_hostip_arr[$x]}" "su - dmdba -c \"${CSS_START_CMD}\""
      # shellcheck disable=SC2181
      if [ $? -eq 0 ]; then
        echo
        color_printf blue "节点 ${wc_hostip_arr[$x]} 启动成功"
        echo
        break
      fi
    done
  done
  #依次启动每个节点的asm实例服务
  for ((x = 0; x < ${#wc_hostip_arr[@]}; x++)); do
    while true; do
      sleep "${WAIT_TIME}"
      exec_ssh_cmd "${wc_hostip_arr[$x]}" "su - dmdba -c \"${ASMSVR_START_CMD}\""
      # shellcheck disable=SC2181
      if [ $? -eq 0 ]; then
        echo
        color_printf blue "节点 ${wc_hostip_arr[$x]} 启动成功"
        echo
        break
      fi
    done
  done
}
#==============================================================#
#                    创建asm镜像磁盘组                         #
#==============================================================#
function create_disk_groups() {
  # 接收参数
  local redundancy=$1 # 冗余级别
  local group_name=$2 # 磁盘组名称
  local disk_str=$3   # 磁盘列表字符串
  local sleep_time=5s
  local dmasmtool_cmd="su - dmdba -c \"$env_dm_home/bin/dmasmtoolm DCR_INI=$data_dir/$dsc_db_name/dmdcr.ini\""
  # 创建 dmasmtool 命令的起始部分
  dmasmtool_cmd+=" <<-\EOF
CREATE DISKGROUP '$(upper "$group_name")' $redundancy REDUNDANCY " #保持单词$redundancy " 后面的空格
  # 将磁盘列表字符串解析成数组
  IFS=',' read -r -a disks_arr <<<"$disk_str"
  # 计算磁盘数量
  local total_disks=${#disks_arr[@]}
  # 根据冗余级别选择不同的分组方式
  #创建asm_disk_paths路径字符串
  create_asm_disk_paths() {
    # 接收参数: 起始磁盘编号、总磁盘数、ASM 磁盘组名、故障组名后缀
    local begin_disks=$1
    local total_disks=$2
    local group_name1=$3
    local failgroup_num=$4
    local asm_disk_paths=""
    # 循环生成 ASM 磁盘路径
    for ((j = begin_disks; j <= total_disks; j++)); do
      # 生成 ASM 磁盘名称和路径
      # shellcheck disable=SC2155
      local asm_disk_name="${group_name1}$(printf '%02d' $j)"
      local asm_disk_path="$ASM_DEVICE_PATH/$asm_disk_name"
      # 将路径添加到列表中
      asm_disk_paths+="'$asm_disk_path',"
    done
    # 移除路径列表中最后一个逗号
    asm_disk_paths="${asm_disk_paths%,}"
    # 构建 DMASM Tool 命令，将路径添加到对应的失败组
    dmasmtool_cmd+="FAILGROUP '${group_name1}${failgroup_num}' ASMDISK $asm_disk_paths " #保持单词$asm_disk_paths " 后面的空格
  }
  case $redundancy in
  "EXTERNAL")
    # 外部冗余，所有磁盘放在同一组中
    create_asm_disk_paths 1 "$total_disks" "$group_name" "01"
    ;;
  "NORMAL")
    # 普通冗余，将磁盘分成两组
    local midpoint=$((total_disks / 2))
    create_asm_disk_paths 1 $midpoint "$group_name" "01"
    create_asm_disk_paths $((midpoint + 1)) "$total_disks" "$group_name" "02"
    ;;
  *)
    # 其他冗余级别，将磁盘分成三组
    local third=$((total_disks / 3))
    create_asm_disk_paths 1 $third "$group_name" "01"
    create_asm_disk_paths $((third + 1)) $((2 * third)) "$group_name" "02"
    create_asm_disk_paths $((2 * third + 1)) "$total_disks" "$group_name" "03"
    ;;
  esac
  # 构建 dmasmtool 命令的完整参数
  dmasmtool_cmd+="ATTRIBUTE AU_SIZE=$au_size, REDO_SIZE=$redo_size
exit
EOF"
  eval "$dmasmtool_cmd"
  # shellcheck disable=SC2181
  while [[ $? -ne 0 ]]; do
    sleep "$sleep_time"
  done
}
#==============================================================#
#                    创建ASM磁盘组                              #
#==============================================================#
function dsc_dmasmtool() {
  log_print "创建ASM磁盘组" ""
  if [[ $dm_install_mode == "dsc" ]]; then
    local dmasmtool_cmd="su - dmdba -c \"$env_dm_home/bin/dmasmtool DCR_INI=$data_dir/$dsc_db_name/dmdcr.ini\""
    local sleep_time=5s
    for ((x = 2; x < ${#disk_arr[@]}; x++)); do
      IFS=',' read -ra disk_arr_temp <<<"${disk_arr[$x]}"
      for ((y = 1; y <= ${#disk_arr_temp[@]}; y++)); do
        if ((y == 1)); then
          dmasmtool_cmd+=" <<-EOF
create diskgroup '$(upper "${asm_disk_arr[$x]}")' asmdisk '$ASM_DEVICE_PATH/${asm_disk_arr[$x]}0$y'
exit
EOF"
        else
          dmasmtool_cmd+=" <<-EOF
alter diskgroup '$(upper "${asm_disk_arr[$x]}")' add asmdisk '$ASM_DEVICE_PATH/${asm_disk_arr[$x]}$(printf "%02d" $y)'
exit
EOF"
        fi
        eval "$dmasmtool_cmd"
        # shellcheck disable=SC2181
        while [[ $? -ne 0 ]]; do
          sleep "$sleep_time"
        done
        # 重置dmasmtool_cmd值，循环执行命令
        dmasmtool_cmd="su - dmdba -c \"$env_dm_home/bin/dmasmtool DCR_INI=$data_dir/$dsc_db_name/dmdcr.ini\""
      done
    done
    echo
  else
    # 定义函数 mode_para_REDUN 用于根据传入的冗余值返回相应的字符串表示
    mode_para_REDUN() {
      local redun_value="$1"
      case $redun_value in
      1)
        echo "EXTERNAL" # 冗余值为 1 时，返回 "EXTERNAL"
        ;;
      2)
        echo "NORMAL" # 冗余值为 2 时，返回 "NORMAL"
        ;;
      *)
        echo "HIGH" # 其他情况下，返回 "HIGH"
        ;;
      esac
    }
    # 声明一个关联数组 asm_redun 用于存储冗余模式
    declare -A asm_redun=(
      [dmlog]="$(mode_para_REDUN "$redo_redun")"
      [dmarch]="$(mode_para_REDUN "$arch_redun")"
      [dmdata]="$(mode_para_REDUN "$data_redun")"
    )
    # 创建asm磁盘组
    for ((i = 1; i < ${#asm_disk_arr[@]}; i++)); do
      element="${asm_disk_arr[$i]}"
      create_disk_groups "${asm_redun[$element]}" "$element" "${disk_arr[$i]}"
    done
  fi
}
#==============================================================#
#                    初始化数据库                               #
#==============================================================#
function init_dsc_db() {
  log_print "初始化数据库实例" ""
  local init_dsc_db_file="$data_dir/$dsc_db_name/dminit.ini"
  lastIndex=$((${#asm_disk_arr[@]} - 1))
  # 定义一个关联数组，包含各个数据库文件的路径和大小
  declare -A paths_and_sizes=(
    [DB_NAME]="$dsc_db_name"                                                          # 数据库db_name名字
    [SYSTEM_PATH]="+$(upper "${asm_disk_arr[$lastIndex]}")/dmdata"                    # 系统文件路径
    [SYSTEM]="+$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/system.dbf" # 系统文件路径和名称
    [SYSTEM_SIZE]="128"                                                               # 系统文件大小
    [ROLL]="+$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/roll.dbf"     # 回滚文件路径和名称
    [ROLL_SIZE]="128"                                                                 # 回滚文件大小
    [MAIN]="+$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/main.dbf"     # 主文件路径和名称
    [MAIN_SIZE]="128"                                                                 # 主文件大小
    [CTL_PATH]="+$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/dm.ctl"   # 控制文件路径和名称
    [LOG_SIZE]="$log_size"                                                            # 日志文件大小
    [DCR_SEQNO]="0"                                                                   # ASM磁盘序列号
    [AUTO_OVERWRITE]="2"                                                              # 自动覆盖策略
    [EXTENT_SIZE]="$extent_size"                                                      # 区大小
    [PAGE_SIZE]="$page_size"                                                          # 页大小
    [CASE_SENSITIVE]="$case_sensitive"                                                # 是否大小写敏感
    [CHARSET]="$charset"                                                              # 字符集
    [BLANK_PAD_MODE]="$blank_pad_mode"                                                # 空格填充模式（打开兼容Oracle参数时候使用）
    [CTL_MIRROR]="$ctl_mirror"                                                        # dm.ctl 和 dm_service.prikey 文件副本数
    [DATA_MIRROR]="$data_mirror"                                                      # DATA_MIRROR: 数据镜副本数
    [LOG_MIRROR]="$log_mirror"                                                        # LOG_MIRROR: 日志镜像副本数
    [DATA_STRIPING]="$data_striping"                                                  # DATA_STRIPING: 数据条带化粒度
    [LOG_STRIPING]="$log_striping"                                                    # LOG_STRIPING: 日志条带化粒度
  )
  # 将路径和大小写入配置文件dminit.ini
  cat <<EOF >"$init_dsc_db_file"
$(for key in "${!paths_and_sizes[@]}"; do
    if [[ $dm_install_mode == "dsc" && $key =~ ^(CTL_MIRROR|DATA_MIRROR|LOG_MIRROR|DATA_STRIPING|LOG_STRIPING)$ ]]; then
      continue
    fi
    printf '%s = %s\n' "$key" "${paths_and_sizes[$key]}"
  done)
EOF
  # 分别针对DSC和DSCM集群参数写到dminit.ini文件中
  if [[ $dm_install_mode == "dsc" ]]; then
    printf "%s\n" "DCR_PATH=$ASM_DEVICE_PATH/${asm_disk_arr[0]}01" >>"$init_dsc_db_file"
  else
    printf "%s\n" "DCR_PATH=$ASM_DEVICE_PATH" >>"$init_dsc_db_file"
  fi
  # 针对每个endpoint写入配置信息到dminit.ini
  a=$([[ $dm_install_mode == "dsc" ]] && echo "2" || echo "1")
  for x in "${!wc_malip_arr[@]}"; do
    if [[ -z "$log_disk" ]]; then
      cat <<EOF >>"$init_dsc_db_file"
[$(upper "${dsc_ep_name}")$x]
config_path = $data_dir/$dsc_db_name/$dsc_ep_name${x}_config
port_num = $port_num
mal_host = ${wc_malip_arr[$x]}
mal_port = ${db_mal_port}
log_path = +$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/$dsc_ep_name${x}_log01.log
log_path = +$(upper "${asm_disk_arr[$lastIndex]}")/dmdata/$dsc_db_name/$dsc_ep_name${x}_log02.log
EOF
    else
      cat <<EOF >>"$init_dsc_db_file"
[$(upper "${dsc_ep_name}")$x]
config_path = $data_dir/$dsc_db_name/$dsc_ep_name${x}_config
port_num = $port_num
mal_host = ${wc_malip_arr[$x]}
mal_port = ${db_mal_port}
log_path = +$(upper "${asm_disk_arr[$a]}")/dmlog/$dsc_db_name/$dsc_ep_name${x}/$dsc_ep_name${x}_log01.log
log_path = +$(upper "${asm_disk_arr[$a]}")/dmlog/$dsc_db_name/$dsc_ep_name${x}/$dsc_ep_name${x}_log02.log
EOF
    fi
  done
  chown dmdba:dinstall "$init_dsc_db_file"
  # 使用dminit命令初始化数据库
  su - dmdba -c "$env_dm_home/bin/dminit control=$init_dsc_db_file"
  printf '\n\033[34m%s\033[0m\n' '数据库初始化完成'
}
#==============================================================#
#                    启动服务                                   #
#==============================================================#
function dsc_start() {
  log_print "注册服务并启动数据库" ""
  cat <<EOF >"${software_dir}/starttmp.sh"
#!/bin/bash
# 删除服务文件（如果存在）
if [[ $os_version -ne 6 ]] && [[ -e /usr/lib/systemd/system/DmService${dsc_db_name}.service ]]; then
  "$env_dm_home"/script/root/dm_service_uninstaller.sh -n DmService"${dsc_db_name}" >/dev/null 2>&1 <<ABC
y
ABC
fi
# 安装服务，并使用指定的参数配置
$env_dm_home/script/root/dm_service_installer.sh \
-t dmserver \
-dm_ini "$data_dir/$dsc_db_name/$dsc_ep_name$((node_num - 1))_config/dm.ini" \
-dcr_ini "$data_dir/$dsc_db_name/dmdcr.ini" \
-p "$dsc_db_name" \
-y "$DM_ASM_SERVICE"
echo
# 等待服务启动
while ! su - dmdba -c "$DB_START_CMD"; do
  sleep 5s
done
EOF
  # 修改数据库参数
  function modify_db_params {
    local node="$1"
    exec_ssh_cmd "${node}" "sed -i '4s/0/180/g' ${data_dir}/${dsc_db_name}/dmdcr.ini"
    exec_ssh_cmd "${node}" "sed -i '6s/0/360/g' ${data_dir}/${dsc_db_name}/dmdcr.ini"
  }
  # 创建归档
  function create_archive {
    local node="$1"
    local number="$2"
    echo
    color_printf blue "在节点 ${node} 创建归档"
    echo
    #在每个节点创建归档
    exec_ssh_cmd "${node}" "su - dmdba -c \"disql -S SYSDBA/SYSDBA:${port_num} '\\\`${scripts_dir}/conf_arch.sql'\""
    while [[ $? -ne 0 ]]; do
      sleep 5s
    done
    if [[ $is_local_arch = "Y" && $arch_disk = "" ]]; then
      exec_ssh_cmd "${node}" "sed -i '4i ARCH_LOCAL_SHARE = 0\n' ${data_dir}/${dsc_db_name}/${dsc_ep_name}${number}_config/dmarch.ini"
    else
      if [[ $dm_install_mode == "dscm" ]]; then
        exec_ssh_cmd "${node}" "sed -i '4i ARCH_ASM_MIRROR=$arch_asm_mirror \n' ${data_dir}/${dsc_db_name}/${dsc_ep_name}${number}_config/dmarch.ini"
        exec_ssh_cmd "${node}" "sed -i '5i ARCH_ASM_STRIPING=$arch_asm_striping \n' ${data_dir}/${dsc_db_name}/${dsc_ep_name}${number}_config/dmarch.ini"
      fi
      exec_ssh_cmd "${node}" "sed -i '6i ARCH_LOCAL_SHARE = 1\n' ${data_dir}/${dsc_db_name}/${dsc_ep_name}${number}_config/dmarch.ini"
      exec_ssh_cmd "${node}" "sed -i '7i ARCH_LOCAL_SHARE_CHECK = 0\n' ${data_dir}/${dsc_db_name}/${dsc_ep_name}${number}_config/dmarch.ini"
    fi
  }
  # 注册服务和启动数据库
  lastIndex=$((${#wc_hostip_arr[@]} - 1))
  for ((x = 0; x < ${#wc_hostip_arr[@]}; x++)); do
    if ((x > 0)); then
      exec_scp_cmd "${data_dir}"/"${dsc_db_name}"/"${dsc_ep_name}"${x}_config "${wc_hostip_arr[$x]}" "${data_dir}"/"${dsc_db_name}" "d"
      exec_ssh_cmd "${wc_hostip_arr[$x]}" "chown -R dmdba:dinstall ${data_dir}/${dsc_db_name}/${dsc_ep_name}${x}_config"
      exec_scp_cmd "${software_dir}"/starttmp.sh "${wc_hostip_arr[$x]}" "${software_dir}"/starttmp.sh "d"
      exec_ssh_cmd "${wc_hostip_arr[$x]}" "sed -i 's/'$dsc_ep_name$((node_num - 1))_config'/'$dsc_ep_name${x}_config'/g' ${software_dir}/starttmp.sh"
    fi
    # 注册服务和启动数据库
    color_printf blue "在节点 ${wc_hostip_arr[$x]} 注册服务并启动数据库"
    echo
    exec_ssh_cmd "${wc_hostip_arr[$x]}" "bash ${software_dir}/starttmp.sh"
    color_printf blue "节点 ${wc_hostip_arr[$x]} 启动成功"
    echo
    # 修改数据库参数
    modify_db_params "${wc_hostip_arr[$x]}"
    # 创建归档
    if ((x == lastIndex)); then
      log_print "创建数据库归档" ""
      # 测试数据库是否正常打开
      while true; do
        PID=$(pgrep -f "$data_dir"/"$dsc_db_name"/"$dsc_ep_name"0_config/dm.ini)
        if [ -n "$PID" ]; then
          connResult=$(execute_sql "select 1" | tail -n 1)
          if [[ $connResult = "1" ]]; then
            break
          fi
        fi
        sleep 0.5s
      done
      for ((y = 0; y < ${#wc_hostip_arr[@]}; y++)); do
        create_archive "${wc_hostip_arr[$y]}" "$y"
      done
      # 优化数据库
      if [[ $optimize_db = "Y" ]]; then
        optimize_dmdb
      fi
    fi
  done
}
#==============================================================#
#                    部署数据库                                 #
#==============================================================#
function install_wc_oguid() {
  # 生成全局oguid
  oguid=$(rand 400000 500000)
}
#==============================================================#
#                    分发其他节点                               #
#==============================================================#
function send_other_node() {
  # 当存在确认监视器时，数组减去1，剩下数组元素都是实时主备元素
  [[ -n "$dw_monitor_ip" ]] && a=$((${#wc_hostip_arr[@]} - 1)) || a=${#wc_hostip_arr[@]}
  # 循环遍历除第一个元素外的每个IP地址，并在其上执行以下操作：
  for ((x = 1; x < a; x++)); do
    color_printf white "拷贝脚本以及安装包到节点：${wc_hostip_arr[$x]}"
    echo
    # 在目标服务器上，如果指定路径下存在文件或目录，则删除它并创建一个新目录。
    exec_ssh_cmd "${wc_hostip_arr[$x]}" "[[ -e ${software_dir} || -d ${software_dir} ]] && rm -rf ${software_dir} ; mkdir -p ${software_dir}"
    # 将本地文件夹中的dwnode.sh复制到目标节点的相应路径中，
    exec_scp_cmd "${software_dir}"/dwnode.sh "${wc_hostip_arr[$x]}" "${software_dir}"/dwnode.sh "d"
    # 并在该文件中追加"-node $((x + 1))"字符串。
    exec_ssh_cmd "${wc_hostip_arr[$x]}" "echo -n \" -node $((x + 1))\" >>${software_dir}/dwnode.sh"
    # 将本地文件夹中的DMShellInstall文件以及可能的iso文件复制到目标节点的相应路径中。
    exec_scp_cmd "${software_dir}"/DMShellInstall "${wc_hostip_arr[$x]}" "${software_dir}" "d"
    if [[ -n "${dm_iso_name}" ]]; then
      exec_scp_cmd "${software_dir}"/"$dm_iso_name" "${wc_hostip_arr[$x]}" "${software_dir}" "d"
    else
      exec_scp_cmd "${software_dir}"/DMInstall.bin "${wc_hostip_arr[$x]}" "${software_dir}" "d"
    fi
  done
}
#==============================================================#
#                    配置特殊参数                               #
#==============================================================#
function mode_wc_para() {
  # 定义脚本名字
  local scripts_name="$1"
  # 定义一个关联数组，用于存储参数和对应的标志
  declare -A flags=(
    ["hostname"]="-hn"
    ["dm_iso_name"]="-di"
    ["timeserver_ip"]="-ti"
    ["key_path"]="-kp"
    ["log_disk"]="-lod"
    ["arch_disk"]="-ard"
    ["vote_disk"]="-vod"
    ["wc_public_ip"]="-dpi"
    ["as_public_ip"]="-api"
    ["link_check_ip"]="-lci"
  )
  # 定义一个空字符串，用于存储参数
  # shellcheck disable=SC2178
  local params=""
  # 遍历关联数组，检查每个参数是否存在，如果存在则将其添加到 params 中
  for key in "${!flags[@]}"; do
    # shellcheck disable=SC2179
    if [[ -n ${!key} ]]; then
      params+=" ${flags[$key]} ${!key}"
    fi
  done
  # shellcheck disable=SC2128
  # 将 params 的值附加到文件 "${software_dir}/dwnode.sh" 中
  echo -n "$params" >>"$scripts_name"
}
#==============================================================#
#                    安装数据守护                               #
#==============================================================#
function install_dw() {
  if [[ "${node_num}" -eq 1 ]]; then
    # 配置oguid
    install_wc_oguid
    # 检查是否需要配置监视器的互信
    [[ "$(ssh_check root "${wc_hostip_arr[@]}")" == "false" ]] && ssh_trust root "${root_passwd}" "${wc_hostip_arr[@]}"
    # 创建安装脚本
    local dw_install_script="${software_dir}/dwnode.sh"
    node_cmd="sh ${software_dir}/DMShellInstall -osp ${os_ssh_port} -rp '${root_passwd}' -dp '${dm_passwd}' \
-d ${env_dm_home} -dd ${data_dir} -ad ${arch_dir} -bd ${backup_dir} -cd ${core_dir} \
-dn ${db_name} -in ${instance_name} -es ${extent_size} -ps ${page_size} -cs ${case_sensitive} -c ${charset} -bpm ${blank_pad_mode} \
-ls ${log_size} -sl ${space_limit} -pn ${port_num} -sp '${sysdba_pwd}' -bm ${backup_mode} \
-m ${only_conf_os} -ud ${install_until_db} -oid ${only_init_db} -opd ${optimize_db} -oopd ${only_optimize_db} -mp ${mem_percent} -om ${oltp_mode} -cm ${compatible_mode} \
-dmi ${wc_mal_ip} -dgn ${dw_group_name} -mpn ${mal_port_num} -mdpn ${mal_dw_port_num} -midpn ${mal_inst_dw_port_num} \
-at ${arch_type} -awa ${arch_wait_apply} -ri ${repeat_interval} \
-oguid ${oguid} \
-dim ${dm_install_mode} -iso ${iso}"
    # 将命令以重定向的方式保存到文件中
    echo -n "$node_cmd" >"$dw_install_script"
    # 配置特殊参数
    mode_wc_para "$dw_install_script"
    # 拷贝提示
    log_print "拷贝脚本以及安装包到部署节点" ""
    # 分发到监视器节点
    if [[ -n "${dw_monitor_ip}" ]]; then
      # 将监视器 IP 传输到备库节点
      echo -n " -dmoi ${dw_monitor_ip}" >>"${dw_install_script}"
      # 创建监视器节点安装脚本
      local dw_monitor_install_script="${software_dir}/dwmonitor_node.sh"
      echo -n "sh ${software_dir}/DMShellInstall -osp ${os_ssh_port}  -rp '${root_passwd}' -dp '${dm_passwd}' -sp '${sysdba_pwd}' \
-d ${env_dm_home} -dd ${data_dir} -ad ${arch_dir} -bd ${backup_dir} -cd ${core_dir} \
-ud Y \
-dmi ${wc_mal_ip} \
-dgn ${dw_group_name} -mdpn ${mal_dw_port_num} -oguid ${oguid} -pn ${port_num} \
-dim ${dm_install_mode} -iso ${iso}" >"${dw_monitor_install_script}"
      # 配置特殊参数
      mode_wc_para "$dw_monitor_install_script"
      # 拷贝脚本以及安装包到监视器节点
      color_printf white "拷贝脚本以及安装包到监视器节点: ${dw_monitor_ip}"
      echo
      # 在目标服务器上，如果指定路径下存在文件或目录，则删除它并创建一个新目录。
      exec_ssh_cmd "${dw_monitor_ip}" "rm -rf ${software_dir} ; mkdir -p ${software_dir}"
      # 将本地文件夹中的dwmonitor_node.sh复制到目标节点的相应路径中，
      exec_scp_cmd "${dw_monitor_install_script}" "${dw_monitor_ip}" "${software_dir}" "d"
      # 并在该文件中追加"-node -1"字符串。
      exec_ssh_cmd "${dw_monitor_ip}" "echo -n ' -node -1' >>${dw_monitor_install_script}"
      # 将本地文件夹中的DMShellInstall文件以及可能的iso文件复制到目标节点的相应路径中。
      exec_scp_cmd "${software_dir}"/DMShellInstall "${dw_monitor_ip}" "${software_dir}" "d"
      if [[ -n "${dm_iso_name}" ]]; then
        exec_scp_cmd "${software_dir}/${dm_iso_name}" "${dw_monitor_ip}" "${software_dir}" "d"
      else
        exec_scp_cmd "${software_dir}/DMInstall.bin" "${dw_monitor_ip}" "${software_dir}" "d"
      fi
    fi
    # 分发节点
    send_other_node
  fi
}
#==============================================================#
#                    安装dsc                                   #
#==============================================================#
function install_dsc() {
  # 生成oguid
  install_wc_oguid
  # 检查是否需要配置监视器的互信
  [[ "$(ssh_check root "${wc_hostip_arr[@]}")" == "false" ]] && ssh_trust root "${root_passwd}" "${wc_hostip_arr[@]}"
  # 创建安装脚本
  local dsc_install_script="${software_dir}/dwnode.sh"
  node_cmd="sh ${software_dir}/DMShellInstall -osp ${os_ssh_port} -rp '${root_passwd}' -dp '${dm_passwd}' \
-d ${env_dm_home} -dd ${data_dir} -ad ${arch_dir} -bd ${backup_dir} -cd ${core_dir} \
-ddn ${dsc_db_name} -den ${dsc_ep_name} -es ${extent_size} -ps ${page_size} -cs ${case_sensitive} -c ${charset} -bpm ${blank_pad_mode} \
-ls ${log_size} -sl ${space_limit} -pn ${port_num} -sp '${sysdba_pwd}' -apd '${asm_pwd}' -bm ${backup_mode} -as ${au_size} -rs ${redo_size} \
-m ${only_conf_os} -ud ${install_until_db} -oid ${only_init_db} -opd ${optimize_db} -oopd ${only_optimize_db} -mp ${mem_percent} -om ${oltp_mode} -cm ${compatible_mode} \
-dmi ${wc_mal_ip} \
-fmd ${format_disk} -dcd ${dcr_disk} -dad ${data_disk} -ila ${is_local_arch} \
-rr ${redo_redun} -ar ${arch_redun} -dr ${data_redun} \
-ctm ${ctl_mirror} -dtm ${data_mirror} -lgm ${log_mirror} -aam ${arch_asm_mirror} -dst ${data_striping} -lst ${log_striping} -aas ${arch_asm_striping} \
-cdp ${css_dcr_ep_port} -adp ${asm_dcr_ep_port} -ddp ${db_dcr_ep_port} -amp ${asvrmal_mal_port} -dmp ${db_mal_port} -oguid ${oguid} \
-dim ${dm_install_mode} -mtp ${multipath} -iso ${iso}"
  # 将命令以重定向的方式保存到文件中
  echo -n "$node_cmd" >"$dsc_install_script"
  # 配置特殊参数
  mode_wc_para "$dsc_install_script"
  # 拷贝提示
  log_print "拷贝脚本以及安装包到部署节点" ""
  # 分发节点
  send_other_node
}
#==============================================================#
#                    数据守护和DSC其他节点执行脚本               #
#==============================================================#
function other_node_shell() {
  # 当存在确认监视器时，数组减去1，剩下数组元素都是实时主备元素
  [[ -n "$dw_monitor_ip" ]] && a=$((${#wc_hostip_arr[@]} - 1)) || a=${#wc_hostip_arr[@]}
  local node_script="${software_dir}/dwnode.sh"
  for ((x = 1; x < a; x++)); do
    log_print "配置节点: ${wc_hostip_arr[$x]}" ""
    color_printf yellow "节点 ${wc_hostip_arr[$x]} 开始配置:"
    exec_ssh_cmd "${wc_hostip_arr[$x]}" "cd ${software_dir} && sh $node_script"
    echo
    color_printf red "节点 ${wc_hostip_arr[$x]} 配置完成."
  done
  if [[ -n "$dw_monitor_ip" ]]; then
    local monitor_script="${software_dir}/dwmonitor_node.sh"
    log_print "配置主机: ${dw_monitor_ip}" ""
    color_printf yellow "监视器 ${dw_monitor_ip} 开始配置:"
    exec_ssh_cmd "${dw_monitor_ip}" "cd ${software_dir} && sh $monitor_script"
    color_printf red "监视器完成配置."
  fi
}
#==============================================================#
#                    达梦数据库基础配置                          #
#==============================================================#
#配置单机归档
function si_dmarch() {
  # 如果归档目录不存在，则创建它
  if [[ ! -d $arch_dir ]]; then
    mkdir -p "$arch_dir"
    chown -R dmdba:dinstall "$arch_dir"
  fi
  # 为特定数据库实例创建归档目录
  db_arch_dir="$arch_dir/$db_name"
  if [[ ! -d $db_arch_dir ]]; then
    mkdir -p "$db_arch_dir"
    chown -R dmdba:dinstall "$db_arch_dir"
  fi
  # 生成配置归档日志的SQL脚本
  conf_arch_sql=$(
    cat <<-EOF
set echo off
set timing off
set feedback off
set lineshow off
ALTER DATABASE MOUNT;
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE ADD ARCHIVELOG 'DEST=$db_arch_dir, TYPE=LOCAL, FILE_SIZE=$log_size, SPACE_LIMIT=$space_limit';
ALTER DATABASE OPEN;
select arch_mode from v\$database;
SELECT arch_name,
        arch_dest,
        arch_file_size,
        arch_space_limit
  FROM v\$dm_arch_ini;
exit;
EOF
  )
  # 将SQL脚本写入文件
  conf_arch_file="${scripts_dir}/conf_arch.sql"
  echo "$conf_arch_sql" >"$conf_arch_file"
  chown dmdba:dinstall "$conf_arch_file"
}
function dsc_dmarch() {
  local dsc_dmarch_file="${scripts_dir}/conf_arch.sql"
  # 添加归档日志目的地到数据库
  add_archive_log_destination() {
    local dest_type=$1         # 归档日志目的地类型（本地或远程）
    local dest_path=$2         # 归档日志目的地路径
    local file_size=$3         # 归档日志文件大小
    local space_limit_value=$4 # 归档日志文件空间限制
    local incoming_path=$5     # 可选的远程归档日志目的地的传入路径
    su - dmdba <<-DMARCH
      cat <<-EOF>>"$dsc_dmarch_file"
ALTER DATABASE ADD ARCHIVELOG 'DEST = $dest_path, TYPE = $dest_type, FILE_SIZE = $file_size, SPACE_LIMIT = $space_limit_value ${incoming_path:+, INCOMING_PATH = $incoming_path}';
EOF
DMARCH
  }
  if [[ $arch_disk = "" && $is_local_arch = "N" ]]; then
    space_limit_value=$space_limit
  else
    space_limit_value=$(awk "BEGIN { printf \"%.0f\", ($space_limit - 10240) / ${#wc_hostip_arr[@]} }")
  fi
  lastIndex=$((${#asm_disk_arr[@]} - 1))
  su - dmdba <<-DMARCH
    cat <<-EOF>"$dsc_dmarch_file"
ALTER DATABASE MOUNT;
EOF
DMARCH
  if [[ -z $arch_disk ]]; then
    if [[ $is_local_arch = "N" ]]; then
      add_archive_log_destination "local" "+$(upper "${asm_disk_arr[$lastIndex]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((node_num - 1))" "$log_size" "$space_limit_value"
      for ((x = 1; x <= ${#wc_hostip_arr[@]}; x++)); do
        if [[ $x -ne $node_num ]]; then
          add_archive_log_destination "REMOTE" "$(upper "${dsc_ep_name}")$((x - 1))" "$log_size" "$space_limit_value" "+$(upper "${asm_disk_arr[$lastIndex]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((x - 1))"
        fi
      done
    else
      add_archive_log_destination "local" "$arch_dir/$dsc_db_name/${dsc_ep_name}$((node_num - 1))" "$log_size" "$space_limit_value"
      for ((x = 1; x <= ${#wc_hostip_arr[@]}; x++)); do
        if [[ $x -ne $node_num ]]; then
          add_archive_log_destination "REMOTE" "$(upper "${dsc_ep_name}")$((x - 1))" "$log_size" "$space_limit_value" "$arch_dir/$dsc_db_name/${dsc_ep_name}$((x - 1))"
        fi
      done
    fi
  else
    if [[ $is_local_arch = "N" ]]; then
      add_archive_log_destination "local" "+$(upper "${asm_disk_arr[$((lastIndex - 1))]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((node_num - 1))" "$log_size" "$space_limit_value"
      for ((x = 1; x <= ${#wc_hostip_arr[@]}; x++)); do
        if [[ $x -ne $node_num ]]; then
          add_archive_log_destination "REMOTE" "$(upper "${dsc_ep_name}")$((x - 1))" "$log_size" "$space_limit_value" "+$(upper "${asm_disk_arr[$((lastIndex - 1))]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((x - 1))"
        fi
      done
    else
      add_archive_log_destination "local" "+$(upper "${asm_disk_arr[$((lastIndex - 1))]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((node_num - 1))" "$log_size" "$space_limit_value"
      for ((x = 1; x <= ${#wc_hostip_arr[@]}; x++)); do
        if [[ $x -ne $node_num ]]; then
          add_archive_log_destination "REMOTE" "$(upper "${dsc_ep_name}")$((x - 1))" "$log_size" "$space_limit_value" "+$(upper "${asm_disk_arr[$((lastIndex - 1))]}")/dmarch/$dsc_db_name/${dsc_ep_name}$((x - 1))"
        fi
      done
      add_archive_log_destination "local" "$arch_dir/$dsc_db_name/${dsc_ep_name}$((node_num - 1))" "$log_size" "$space_limit_value"
    fi
  fi
  su - dmdba <<-DMARCH
    cat <<-EOF>>$dsc_dmarch_file
ALTER DATABASE ARCHIVELOG;
ALTER DATABASE OPEN;
exit;
EOF
DMARCH
  chown dmdba:dinstall "$dsc_dmarch_file"
}
#==============================================================#
#                    达梦数据库备份                             #
#==============================================================#
function dm_bak() {
  # 仅创建实例时，判断备份路径是否存在，没有则创建
  if [[ $only_init_db = "Y" ]]; then
    if [[ ! -d $backup_dir ]]; then
      mkdir "$backup_dir"
      chown -R dmdba:dinstall "$backup_dir"
    fi
  fi
  # 创建实例备份路径
  [[ ! -d $backup_dir/$1 ]] && mkdir -p "$backup_dir"/"$1"
  chown -R dmdba:dinstall "$backup_dir"/"$1"
  cat <<-EOF >${scripts_dir}/conf_fullbackup.sql
set echo off
set timing off
set feedback off
set lineshow off
SP_INIT_JOB_SYS(1);
--call SP_DROP_JOB('bak_full');
--call SP_DROP_JOB('bak_arch');
call SP_CREATE_JOB('bak_full',1,0,'',0,0,'',0,'每天全量备份，并删除30天之前的备份。');
call SP_JOB_CONFIG_START('bak_full');
call SP_JOB_SET_EP_SEQNO('bak_full', 0);
call SP_ADD_JOB_STEP('bak_full', 'bak_full', 6, '01000000$backup_dir/$1', 3, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_STEP('bak_full', 'bak_del', 0, 'CALL SF_BAKSET_BACKUP_DIR_ADD(''DISK'',''$backup_dir/$1'');
CALL SP_DB_BAKSET_REMOVE_BATCH(''DISK'',NOW()-30);', 1, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('bak_full', 'bak_full', 1, 1, 1, 0, 0, '01:05:00', NULL, '2020-01-01 00:00:00', NULL, '');
call SP_JOB_CONFIG_COMMIT('bak_full');
call SP_CREATE_JOB('bak_arch',1,0,'',0,0,'',0,'每天备份归档，删除30天之前的备份');
call SP_JOB_CONFIG_START('bak_arch');
call SP_JOB_SET_EP_SEQNO('bak_arch', 0);
call SP_ADD_JOB_STEP('bak_arch', 'bak_arch', 6, '30000000$backup_dir/$1', 3, 0, 0, 0, NULL, 0);
call SP_ADD_JOB_STEP('bak_arch', 'bak_del', 0, 'CALL SF_BAKSET_BACKUP_DIR_ADD(''DISK'',''$backup_dir/$1'');
CALL SP_ARCH_BAKSET_REMOVE_BATCH(''DISK'',NOW()-30);', 0, 0, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('bak_arch', 'back_arch', 1, 1, 1, 0, 0, '03:05:00', NULL, '2023-05-24 18:16:05', NULL, '');
call SP_JOB_CONFIG_COMMIT('bak_arch');
call SP_ENABLE_JOB('bak_arch', 0);
SELECT NAME,
       describe
  FROM sysjob.sysjobs;
SELECT top 1 NAME,
       command
  FROM "SYSJOB"."SYSJOBSTEPS";
exit;
EOF
  cat <<-EOF >${scripts_dir}/conf_incrbackup.sql
set echo off
set timing off
set feedback off
set lineshow off
SP_INIT_JOB_SYS(1);
--call SP_DROP_JOB('bak_full');
--call SP_DROP_JOB('bak_inc');
--call SP_DROP_JOB('bak_arch');
call SP_CREATE_JOB('bak_full',1,0,'',0,0,'',0,'周六全量备份，并删除30天之前的备份。');
call SP_JOB_CONFIG_START('bak_full');
call SP_JOB_SET_EP_SEQNO('bak_full', 0);
call SP_ADD_JOB_STEP('bak_full', 'bak_full', 6, '01000000$backup_dir/$1', 3, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_STEP('bak_full', 'bak_del', 0, 'CALL SF_BAKSET_BACKUP_DIR_ADD(''DISK'',''$backup_dir/$1'');
CALL SP_DB_BAKSET_REMOVE_BATCH(''DISK'',NOW()-30);', 1, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('bak_full', 'bak_full', 1, 2, 1, 64, 0, '01:05:00', NULL, '2020-01-01 00:00:00', NULL, '');
call SP_JOB_CONFIG_COMMIT('bak_full');
call SP_CREATE_JOB('bak_inc',1,0,'',0,0,'',0,'周日到周五做增量备份,如果失败,清除8天前备份,做全量备份');
call SP_JOB_CONFIG_START('bak_inc');
call SP_JOB_SET_EP_SEQNO('bak_inc', 0);
call SP_ADD_JOB_STEP('bak_inc', 'bak_inc', 6, '11000000$backup_dir/$1|$backup_dir/$1', 1, 3, 2, 6, NULL, 0);
call SP_ADD_JOB_STEP('bak_inc', 'bak_del', 0, 'SF_BAKSET_BACKUP_DIR_ADD(''DISK'',''$backup_dir/$1'');
CALL SP_DB_BAKSET_REMOVE_BATCH(''DISK'',NOW()-8);', 3, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_STEP('bak_inc', 'switch_bak_full', 6, '01000000$backup_dir/$1', 1, 1, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('bak_inc', 'bak_inc', 1, 2, 1, 63, 0, '01:05:00', NULL, '2020-01-01 00:00:00', NULL, '');
call SP_JOB_CONFIG_COMMIT('bak_inc');
call SP_CREATE_JOB('bak_arch',1,0,'',0,0,'',0,'每天备份归档，删除30天之前的备份');
call SP_JOB_CONFIG_START('bak_arch');
call SP_JOB_SET_EP_SEQNO('bak_arch', 0);
call SP_ADD_JOB_STEP('bak_arch', 'bak_arch', 6, '30000000$backup_dir/$1', 3, 0, 0, 0, NULL, 0);
call SP_ADD_JOB_STEP('bak_arch', 'bak_del', 0, 'CALL SF_BAKSET_BACKUP_DIR_ADD(''DISK'',''$backup_dir/$1'');
CALL SP_ARCH_BAKSET_REMOVE_BATCH(''DISK'',NOW()-30);', 0, 0, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('bak_arch', 'back_arch', 1, 1, 1, 0, 0, '03:05:00', NULL, '2023-05-24 18:16:05', NULL, '');
call SP_JOB_CONFIG_COMMIT('bak_arch');
call SP_ENABLE_JOB('bak_arch', 0);
SELECT NAME,
       describe
  FROM sysjob.sysjobs;
SELECT top 1 NAME,
       command
  FROM "SYSJOB"."SYSJOBSTEPS";
exit;
EOF
  cat <<-EOF >${scripts_dir}/check_backup.sql
DECLARE
    v_job_id sysjob.sysjobs.id%TYPE;
BEGIN
    -- 1. 从 sysjob.sysjobs 表中选择 id，条件是 name 等于 'bak_full'。
    BEGIN
        SELECT id INTO v_job_id
        FROM sysjob.sysjobs
        WHERE upper(name) = 'BAK_FULL';
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            DBMS_OUTPUT.PUT_LINE('没有找到名称为 BAK_FULL 的作业。');
            RETURN;
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('发生错误: ' || SQLERRM);
            RETURN;
    END;
    -- 2. 调用 sp_dbms_job_run 存储过程来运行作业。
    BEGIN
        EXECUTE IMMEDIATE 'call sp_dbms_job_run(' || v_job_id || ')';
    EXCEPTION
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('运行作业时发生错误: ' || SQLERRM);
            RETURN;
    END;
    -- 3. 调用SF_BAKSET_BACKUP_DIR_ADD('DISK', '$backup_dir/$1')把备份加到数据库里面
    BEGIN
        EXECUTE IMMEDIATE 'call sf_bakset_backup_dir_add(''DISK'', ''$backup_dir/$1'')';
    EXCEPTION
        WHEN OTHERS THEN
            DBMS_OUTPUT.PUT_LINE('调用存储过程 sf_bakset_backup_dir_add 时发生错误: ' || SQLERRM);
            RETURN;
    END;
    -- 4. 从 V$BACKUPSET 查看备份集
    FOR hist_rec IN (SELECT DEVICE_TYPE, BACKUP_PATH, TYPE, RANGE# FROM V\$BACKUPSET) LOOP
        -- 在这里你可以对每条记录进行处理，比如打印出相关信息
        DBMS_OUTPUT.PUT_LINE('备份集: ' || hist_rec.BACKUP_PATH || '.');
    END LOOP;
END;
/
exit;
EOF
  chown dmdba:dinstall ${scripts_dir}/conf_fullbackup.sql
  chown dmdba:dinstall ${scripts_dir}/conf_incrbackup.sql
  chown dmdba:dinstall ${scripts_dir}/check_backup.sql
  ls -lh ${scripts_dir}/conf_fullbackup.sql && ls -lh ${scripts_dir}/conf_incrbackup.sql && ls -lh ${scripts_dir}/check_backup.sql
}
#==============================================================#
#                       创建dmdba用户                           #
#==============================================================#
function conf_arch_bak_user() {
  # 检查必要参数是否存在
  if [[ -z "$dm_install_mode" ]] || [[ -z "$sysdba_pwd" ]]; then
    color_printf red "必要参数 dm_install_mode 或 sysdba_pwd 不存在"
    return 1
  fi
  # 仅优化数据库时，
  if [[ $only_optimize_db == "Y" && $optimize_db == "Y" ]]; then
    db_name=$(execute_sql "select name from v\\\$database" | tail -n 1)
  fi
  # 创建归档和备份脚本
  log_print "创建归档和备份脚本" ""
  if [[ $dm_install_mode = "dsc" || $dm_install_mode = "dscm" ]]; then
    dsc_dmarch
  else
    si_dmarch
  fi
  # 输出归档脚本路径
  color_printf blue "创建数据库归档脚本："
  echo
  ls -lh ${scripts_dir}/conf_arch.sql
  echo
  # 创建备份脚本
  color_printf blue "创建数据库备份脚本："
  echo
  if [[ $dm_install_mode = "dsc" ]]; then
    dm_bak "$dsc_db_name"
  else
    dm_bak "$db_name"
  fi
  # 创建 DMDBA 用户
  echo
  color_printf blue "创建 DMDBA 用户脚本，密码 $sysdba_pwd ："
  cat <<-EOF >${scripts_dir}/create_user.sql
set timing off
set lineshow off
set feedback off
create user "DMDBA" identified by "$sysdba_pwd" password_policy 0 limit failed_login_attemps unlimited, password_life_time unlimited, password_lock_time unlimited;
grant "DBA","PUBLIC","RESOURCE","SOI","VTI" to "DMDBA" with admin option;
-- 修改 SYSDBA 密码
alter user "SYSDBA" identified by "$sysdba_pwd" password_policy 0 limit failed_login_attemps unlimited, password_life_time unlimited, password_lock_time unlimited;
-- 查看密码策略
select username from dba_users where account_status='OPEN';
exit;
EOF
  # 修改脚本所有权
  chown dmdba:dinstall ${scripts_dir}/create_user.sql
  # 输出 DMDBA 用户脚本路径
  echo
  ls -lh ${scripts_dir}/create_user.sql
  # 检查命令执行是否成功，如果失败则输出错误信息并退出函数
  # shellcheck disable=SC2181
  if [[ $? -ne 0 ]]; then
    color_printf red "创建归档和备份脚本以及 DMDBA 用户脚本失败，请检查日志"
    return 1
  fi
}
#==============================================================#
#                    达梦数据库优化配置                          #
#==============================================================#
function optimize_dmini() {
  log_print "创建达梦数据库优化脚本" ""
  local conf_script="${scripts_dir}/conf_dmini.sql"
  local query_script="${scripts_dir}/query_dmini.sql"
  local conf_statistics="${scripts_dir}/conf_statistics.sql"
  # 创建数据库参数配置脚本
  color_printf blue "创建数据库参数配置脚本:"
  echo
  local is_dsc=0
  if [[ $dm_install_mode == "dsc" || $dm_install_mode == "dscm" ]]; then
    is_dsc=1
  fi
  cat <<-EOF >"${conf_script}"
SET echo OFF
SET serveroutput ON
SET timing OFF
SET feedback OFF
SET lineshow OFF
declare
    exec_mode            int := 0;              --0表示直接执行脚本修改参数，1表示不直接修改参数，打印设置参数的语句，设置为1后，必须调整v_mem_mb和v_cpus
    is_dsc               int := $is_dsc;        --是否是dsc集群,如果是dsc集群请设置为1，将自动调整dsc相关参数
    mem_per              int := $mem_percent;   --默认所有的内存归达梦数据库使用，如实际不能100%可用，可以调整此参数; MAX_OS_MEMORY强制100不与此参数挂钩
    v_mem_mb             int := 16000;          --exec_mode为1时请自行根据机器实际内存调整此参数，单位为M
    v_cpus               int := 8;              --exec_mode为1时请自行根据机器实际CPU核数调整此参数
    oltp_mode            int := $oltp_mode;     --并发量较高的OLTP类型系统此参数设置为1，并发量不高的一般业务系统和OLAP类的系统此参数设置为0,影响SORT_FLAG和UNDO_RETENTION
    pk_cluster_mode      int := 0;              --是否使用聚集主键：性能要求高且大字段较少的业务场景强烈建议设置为1，大字段多的场景设置为0
    ini_bak              int := 1;              --是否建一个表备份老的dm.ini,1为保存，0为不保存，默认不保存
    tname                varchar(100);
    MEMORY_POOL          int;
    MEMORY_N_POOLS       int;
    MEMORY_TARGET        int;
    BUFFER               INT;
    MAX_BUFFER           INT;
    RECYCLE              int;
    CACHE_POOL_SIZE      int;
    BUFFER_POOLS         int;
    RECYCLE_POOLS        int;
    SORT_BUF_SIZE        int;
    SORT_BUF_GLOBAL_SIZE INT;
    DICT_BUF_SIZE        INT;
    HJ_BUF_SIZE          INT;
    HAGR_BUF_SIZE        INT;
    HJ_BUF_GLOBAL_SIZE   INT;
    HAGR_BUF_GLOBAL_SIZE INT;
    SORT_FLAG            INT;
    SORT_BLK_SIZE        INT;
    RLOG_POOL_SIZE       INT;
    TASK_THREADS         INT;
    IO_THR_GROUPS        INT;
    FAST_POOL_PAGES      INT :=3000;
    FAST_ROLL_PAGES      INT :=1000;
    UNDO_RETENTION       INT :=90;
    CNT                  INT;
    VER1                 INT :=0;
    VER2                 INT :=0;
begin
    CNT :=0;
    if exec_mode=0 then
        SELECT TOP 1 N_CPU,
               TOTAL_PHY_SIZE/1024/1024
          INTO
               v_cpus,
               v_mem_mb
          FROM V\$SYSTEMINFO;
    end if;
    v_mem_mb := v_mem_mb * (mem_per/100.0);
    v_mem_mb     =round(v_mem_mb,-3);
    IF v_mem_mb <= 2000 THEN
        goto return_2000;
    END IF;
    IF v_mem_mb > 512000 THEN
        v_mem_mb :=v_mem_mb*0.8;
    END IF;
    MEMORY_TARGET=round(cast(v_mem_mb * 0.12 as int),-3);
    TASK_THREADS  :=4;
    IO_THR_GROUPS :=8;
    IF v_cpus > 64 THEN
        v_cpus        := 64;
        TASK_THREADS  :=16;
        IO_THR_GROUPS :=32;
    END IF;
    IF v_cpus <= 64 THEN
        IO_THR_GROUPS :=16;
        TASK_THREADS  :=8;
    END IF;
    IF v_cpus <= 8 THEN
        TASK_THREADS  :=4;
        IO_THR_GROUPS :=8;
    END IF;
    BUFFER  := round(cast(v_mem_mb * 0.4 as int),-3);
    RECYCLE :=cast(v_mem_mb * 0.04 as int);
    IF v_mem_mb < 70000 THEN
        with t as
             ( select rownum rn
                from dual
                     connect by level <= 100
             )
             ,
             t1 as
             ( select * from t where rn > 1
             minus
             select ta.rn * tb.rn
               from t ta,
                    t tb
              where ta.rn <= tb.rn
                and ta.rn > 1
                and tb.rn > 1
             )
          select top 1 rn
            into
                 BUFFER_POOLS
            from t1
           where rn > v_mem_mb/800
        order by 1;
    ELSE
        BUFFER_POOLS := 101;
    END IF;
    --修改内存池
    IF v_mem_mb   >= 16000 THEN
        IF v_mem_mb= 16000 THEN
            MEMORY_POOL          := 1500;
            SORT_BUF_GLOBAL_SIZE := 1000;
            MEMORY_N_POOLS       := 3;
            CACHE_POOL_SIZE      := 512;
        ELSE
            MEMORY_POOL          := 2000;
            SORT_BUF_GLOBAL_SIZE := 2000;
            MEMORY_N_POOLS       := 11;
            CACHE_POOL_SIZE      := 1024;
        END IF;
        FAST_POOL_PAGES :=9999;
        SORT_FLAG    = 0;
        SORT_BLK_SIZE=1;
        SORT_BUF_SIZE        := 10;
        RLOG_POOL_SIZE       := 1024;
        HJ_BUF_GLOBAL_SIZE   := LEAST(cast(v_mem_mb * 0.0625 as int),10000);
        HAGR_BUF_GLOBAL_SIZE := LEAST(cast(v_mem_mb * 0.0625 as int),10000);
        HJ_BUF_SIZE          :=250;
        HAGR_BUF_SIZE        :=250;
        IF v_mem_mb >= 64000 THEN
            FAST_POOL_PAGES :=99999;
            FAST_ROLL_PAGES :=9999;
            BUFFER          :=BUFFER-3000;
            CACHE_POOL_SIZE := 2048;
            RLOG_POOL_SIZE  := 2048;
            SORT_FLAG        = 0;
            SORT_BLK_SIZE    = 1;
            SORT_BUF_SIZE    = 20;
            SORT_BUF_GLOBAL_SIZE= cast(v_mem_mb * 0.04 as    int);
            HJ_BUF_GLOBAL_SIZE   := cast(v_mem_mb * 0.15625 as int);
            HAGR_BUF_GLOBAL_SIZE := cast(v_mem_mb * 0.04 as    int);
            HJ_BUF_SIZE          :=512;
            HAGR_BUF_SIZE        :=512;
            MEMORY_N_POOLS       := 59;
            IF v_mem_mb            >= 128000 THEN
                SORT_FLAG           = 1;
                SORT_BLK_SIZE       =1;
                SORT_BUF_SIZE       =50;
                SORT_BUF_GLOBAL_SIZE= cast(v_mem_mb * 0.1 as int);
            END IF;
            IF v_mem_mb            >= 256000 THEN
                SORT_FLAG           = 1;
                SORT_BLK_SIZE       =2;
                SORT_BUF_SIZE       =50;
                SORT_BUF_GLOBAL_SIZE= cast(v_mem_mb * 0.1 as int);
            END IF;
        END IF;
        DICT_BUF_SIZE        := 50;
        HJ_BUF_GLOBAL_SIZE   :=round(HJ_BUF_GLOBAL_SIZE,-3);
        HAGR_BUF_GLOBAL_SIZE :=round(HAGR_BUF_GLOBAL_SIZE,-3);
        SORT_BUF_GLOBAL_SIZE :=round(SORT_BUF_GLOBAL_SIZE,-3);
        RECYCLE              :=round(RECYCLE,-3);
    ELSE
        MEMORY_POOL          :=GREAT(cast(v_mem_mb * 0.0625 as int),100);
        MEMORY_POOL          :=round(MEMORY_POOL,-2);
        MEMORY_N_POOLS       := 2;
        CACHE_POOL_SIZE      := 200;
        RLOG_POOL_SIZE       := 256;
        SORT_BUF_SIZE        := 10;
        SORT_BUF_GLOBAL_SIZE := 500;
        DICT_BUF_SIZE        := 50;
        SORT_FLAG    = 0;
        SORT_BLK_SIZE=1;
        HJ_BUF_GLOBAL_SIZE   := GREAT(cast(v_mem_mb * 0.0625 as  int),500);
        HAGR_BUF_GLOBAL_SIZE := GREAT(cast(v_mem_mb * 0.0625 as  int),500);
        HJ_BUF_SIZE          := GREAT(cast(v_mem_mb * 0.00625 as int),50);
        HAGR_BUF_SIZE        :=GREAT(cast(v_mem_mb * 0.00625 as  int),50);
    END IF;
    --设置根据RECYCLE情况RECYCLE_POOLS参数
    with t as
         ( select rownum rn
            from dual
                 connect by level <= 100
         )
         ,
         t1 as
         ( select * from t where rn > 1
         minus
         select ta.rn * tb.rn
           from t ta,
                t tb
          where ta.rn <= tb.rn
            and ta.rn > 1
            and tb.rn > 1
         )
      select top 1 rn
        into
             RECYCLE_POOLS
        from t1
       where rn <= great(RECYCLE*1024/3000/(page()/1024),2)
    order by 1 desc;
    tname :='BAK_DMINI_' || to_char(sysdate,'yymmdd');
    execute IMMEDIATE 'select count(*) from USER_ALL_TABLES where table_name= ?' into CNT using tname;
    if exists(select 1 from V\$INSTANCE where MODE\$ in ('NORMAL','PRIMARY')) then
        IF CNT=0 and ini_bak=1 THEN
            execute IMMEDIATE 'CREATE TABLE BAK_DMINI_' || to_char(sysdate,'yymmdd') || ' as select *,sysdate uptime from V\$dm_ini';
        ELSE
            IF CNT=1 THEN
                execute IMMEDIATE 'INSERT INTO  BAK_DMINI_' || to_char(sysdate,'yymmdd') || ' select *,sysdate uptime from V\$dm_ini';
                COMMIT;
            END IF;
        END IF;
    end if;
    --如果oltp_mode设置为1，采用旧的排序模式,undo_relation采用默认值
    if oltp_mode  =1 then
        SORT_FLAG = 0;
        SORT_BUF_SIZE := 2;
    end if;
    --如果oltp_mode设置为0，undo_relation适当放大,采用新的排序方法
    if oltp_mode       =0 then
        UNDO_RETENTION = 900;
    end if;
    MAX_BUFFER := BUFFER;
    --TRUNC SVN号134202版本V\$instance视图引入build_version字段
    IF EXISTS (SELECT 1
           FROM (select * from V\$VERSION where REGEXP_LIKE(BANNER, '^[0-9]'))
          where BANNER LIKE '%-%'
            and SUBSTRING_INDEX(SUBSTRING_INDEX(BANNER, '-', 3), '-', -1)>'134202') THEN
        SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(build_version, '-', 2), '-', -1) a ,
               SUBSTRING_INDEX(SUBSTRING_INDEX(build_version, '-', 3), '-', -1) b
          INTO
               VER1,
               VER2
          from V\$instance;
    END IF;
    IF exec_mode=0 THEN
        --修改cpu相关参数
        SP_SET_PARA_VALUE(2,'WORKER_THREADS',v_cpus);
        SP_SET_PARA_VALUE(2,'IO_THR_GROUPS',IO_THR_GROUPS);
        --将此参数改为0
        SP_SET_PARA_VALUE(2,'GEN_SQL_MEM_RECLAIM',0);
        --修改内存池相关参数
        SP_SET_PARA_VALUE(2,'MAX_OS_MEMORY', 100);
        SP_SET_PARA_VALUE(2,'MEMORY_POOL', MEMORY_POOL);
        SP_SET_PARA_VALUE(2,'MEMORY_N_POOLS', MEMORY_N_POOLS);
        SP_SET_PARA_VALUE(2,'MEMORY_TARGET', MEMORY_TARGET);
        --修改内存检测参数为1
        SP_SET_PARA_VALUE(2,'MEMORY_MAGIC_CHECK', 1);
        --修改缓冲区相关参数
        SP_SET_PARA_VALUE(2,'BUFFER', BUFFER);
        --新版本已去掉MAX_BUFFER参数，如果存在就修改
        IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='MAX_BUFFER') THEN
            SP_SET_PARA_VALUE(2,'MAX_BUFFER', MAX_BUFFER);
        END IF;
        SP_SET_PARA_VALUE(2,'BUFFER_POOLS', BUFFER_POOLS);
        SP_SET_PARA_VALUE(2,'RECYCLE', RECYCLE);
        SP_SET_PARA_VALUE(2,'RECYCLE_POOLS', RECYCLE_POOLS);
        --修改fast_pool相关参数，如果是dsc环境，适当放小，以免影响启动速度
        IF is_dsc= 1 THEN
            SP_SET_PARA_VALUE(2,'FAST_POOL_PAGES', 10000);
            SP_SET_PARA_VALUE(2,'FAST_ROLL_PAGES', 3000);
            SP_SET_PARA_VALUE(2,'TASK_THREADS', 16);
        ELSE
            SP_SET_PARA_VALUE(2,'FAST_POOL_PAGES', FAST_POOL_PAGES);
            SP_SET_PARA_VALUE(2,'FAST_ROLL_PAGES', FAST_ROLL_PAGES);
            SP_SET_PARA_VALUE(2,'TASK_THREADS',TASK_THREADS);
            --如果不是dsc环境，开启热页动态加载，关闭预读
            SP_SET_PARA_VALUE(2,'ENABLE_FREQROOTS',1);
            SP_SET_PARA_VALUE(2,'MULTI_PAGE_GET_NUM',1);
            SP_SET_PARA_VALUE(2,'PRELOAD_SCAN_NUM',0);
            SP_SET_PARA_VALUE(2,'PRELOAD_EXTENT_NUM',0);
        END IF;
        --修改HASH相关参数
        SP_SET_PARA_VALUE(1,'HJ_BUF_GLOBAL_SIZE', HJ_BUF_GLOBAL_SIZE);
        SP_SET_PARA_VALUE(1,'HJ_BUF_SIZE', HJ_BUF_SIZE );
        SP_SET_PARA_VALUE(1,'HAGR_BUF_GLOBAL_SIZE',HAGR_BUF_GLOBAL_SIZE);
        SP_SET_PARA_VALUE(1,'HAGR_BUF_SIZE', HAGR_BUF_SIZE );
        --修改排序相关参数
        SP_SET_PARA_VALUE(2,'SORT_FLAG',SORT_FLAG);
        SP_SET_PARA_VALUE(2,'SORT_BLK_SIZE',SORT_BLK_SIZE);
        SP_SET_PARA_VALUE(2,'SORT_BUF_SIZE', SORT_BUF_SIZE);
        SP_SET_PARA_VALUE(2,'SORT_BUF_GLOBAL_SIZE', SORT_BUF_GLOBAL_SIZE);
        --修改其他内存参数
        SP_SET_PARA_VALUE(2,'RLOG_POOL_SIZE', RLOG_POOL_SIZE);
        SP_SET_PARA_VALUE(2,'CACHE_POOL_SIZE', CACHE_POOL_SIZE);
        SP_SET_PARA_VALUE(2,'DICT_BUF_SIZE', DICT_BUF_SIZE);
        SP_SET_PARA_VALUE(2,'VM_POOL_TARGET', 16384);
        SP_SET_PARA_VALUE(2,'SESS_POOL_TARGET', 16384);
        --修改实例相关参数
        SP_SET_PARA_VALUE(2,'USE_PLN_POOL', 1);
        SP_SET_PARA_VALUE(2,'ENABLE_MONITOR', 1);
        SP_SET_PARA_VALUE(2,'SVR_LOG', 0);
        SP_SET_PARA_VALUE(2,'TEMP_SIZE', 1024);
        SP_SET_PARA_VALUE(2,'TEMP_SPACE_LIMIT', 102400);
        SP_SET_PARA_VALUE(2,'MAX_SESSIONS', 1500);
        SP_SET_PARA_VALUE(2,'MAX_SESSION_STATEMENT', 20000);
        --性能要求高且大字段较少的业务场景建议设置为1，大字段多的场景设置为0
        if pk_cluster_mode = 1 then
            SP_SET_PARA_VALUE(2,'PK_WITH_CLUSTER',1);
        else
            SP_SET_PARA_VALUE(2,'PK_WITH_CLUSTER',0);
        end if;
        SP_SET_PARA_VALUE(2,'ENABLE_ENCRYPT',0);
        --修改优化器相关参数
        SP_SET_PARA_VALUE(2,'OLAP_FLAG',2);
        SP_SET_PARA_VALUE(2,'VIEW_PULLUP_FLAG',1);
        SP_SET_PARA_VALUE(2,'OPTIMIZER_MODE',1);
        SP_SET_PARA_VALUE(2,'ADAPTIVE_NPLN_FLAG',0);
        IF VER1>0 AND VER1<=3 AND VER2<=163 THEN
            SP_SET_PARA_VALUE(2,'OPTIMIZER_OR_NBEXP',0);
        END IF;
        IF VER1>0 AND VER1<=3 AND VER2<=153 THEN
            IF EXISTS (SELECT *
                   FROM V\$DM_INI
                  WHERE PARA_NAME = 'GROUP_OPT_FLAG'
                    and DEFAULT_VALUE=60) THEN
                SP_SET_PARA_VALUE(2,'GROUP_OPT_FLAG',52);
            END IF;
        END IF;
        --开启并行PURGE
        SP_SET_PARA_VALUE(2,'PARALLEL_PURGE_FLAG',1);
        --开启手动并行
        SP_SET_PARA_VALUE(2,'PARALLEL_POLICY',2);
        SP_SET_PARA_DOUBLE_VALUE(2,'UNDO_RETENTION',UNDO_RETENTION);
        --UNDO_RETENTION如果放大，可以适当调大UNDO_EXTENT_NUM。负载高的时候，减少文件系统的申请/释放操作。
        SP_SET_PARA_VALUE(2,'UNDO_EXTENT_NUM',16);
        --开启SQL 注入HINT功能
        SP_SET_PARA_VALUE(2,'ENABLE_INJECT_HINT',1);
        SP_SET_PARA_VALUE(2,'FAST_LOGIN',1);
        SP_SET_PARA_VALUE(2,'BTR_SPLIT_MODE',1);
        --关闭参数监控
        SP_SET_PARA_VALUE(2,'ENABLE_MONITOR_BP',0);
        --SLCT_OPT_FLAG参数设置为0
        IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='SLCT_OPT_FLAG') THEN
            SP_SET_PARA_VALUE(1,'SLCT_OPT_FLAG',0);
        END IF;
        IF is_dsc= 1 THEN
            SP_SET_PARA_VALUE(2,'ENABLE_FREQROOTS',0);
            --DSC关闭数据页预加载参数
            SP_SET_PARA_VALUE(2,'MULTI_PAGE_GET_NUM',1);
            SP_SET_PARA_VALUE(2,'PRELOAD_SCAN_NUM',0);
            SP_SET_PARA_VALUE(2,'PRELOAD_EXTENT_NUM',0);
            SP_SET_PARA_VALUE(2,'DSC_N_POOLS',MEMORY_N_POOLS);
            IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='DSC_GBS_REVOKE_OPT') THEN
                SP_SET_PARA_VALUE(2,'DSC_GBS_REVOKE_OPT',0);
            END IF;
            SP_SET_PARA_VALUE(2,'DSC_HALT_SYNC',0);
            SP_SET_PARA_VALUE(2,'DSC_N_CTLS',50000);
            SP_SET_PARA_VALUE(2,'DSC_ENABLE_MONITOR',0);
            SP_SET_PARA_VALUE(2,'TRX_DICT_LOCK_NUM',5);
            SP_SET_PARA_VALUE(2,'DIRECT_IO',1);
        END IF;
    ELSE
        --修改cpu相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''WORKER_THREADS'','||v_cpus||');';
        PRINT 'SP_SET_PARA_VALUE(2,''IO_THR_GROUPS'','||IO_THR_GROUPS||');';
        PRINT 'SP_SET_PARA_VALUE(2,''GEN_SQL_MEM_RECLAIM'',0);';
        --修改内存池相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''MAX_OS_MEMORY'',       '||100||');';
        PRINT 'SP_SET_PARA_VALUE(2,''MEMORY_POOL'',         '||MEMORY_POOL||');';
        PRINT 'SP_SET_PARA_VALUE(2,''MEMORY_N_POOLS'',      '||MEMORY_N_POOLS||');';
        PRINT 'SP_SET_PARA_VALUE(2,''MEMORY_TARGET'',       '||MEMORY_TARGET||');';
        --修改缓冲区相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''BUFFER'',              '||BUFFER||');';
        --新版本已去掉MAX_BUFFER参数，如果存在就修改
        IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='MAX_BUFFER') THEN
            PRINT 'SP_SET_PARA_VALUE(2,''MAX_BUFFER'',          '||MAX_BUFFER||');';
        END IF;
        PRINT 'SP_SET_PARA_VALUE(2,''BUFFER_POOLS'',        '||BUFFER_POOLS||');';
        PRINT 'SP_SET_PARA_VALUE(2,''RECYCLE'',        	   '||RECYCLE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''RECYCLE_POOLS'',       '||RECYCLE_POOLS||');';
        --修改fast_pool相关参数，如果是dsc环境，适当放小，以免影响启动速度
        IF is_dsc= 1 THEN
            PRINT 'SP_SET_PARA_VALUE(2,''FAST_POOL_PAGES'',  10000);';
            PRINT 'SP_SET_PARA_VALUE(2,''FAST_ROLL_PAGES'',   3000);';
            PRINT 'SP_SET_PARA_VALUE(2,''TASK_THREADS'',     16);';
        ELSE
            PRINT 'SP_SET_PARA_VALUE(2,''FAST_POOL_PAGES'',     '||FAST_POOL_PAGES||');';
            PRINT 'SP_SET_PARA_VALUE(2,''FAST_ROLL_PAGES'',     '||FAST_ROLL_PAGES||');';
            --如果不是dsc环境，开启热页动态加载，关闭预读
            PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_FREQROOTS'',1);';
            PRINT 'SP_SET_PARA_VALUE(2,''MULTI_PAGE_GET_NUM'',1);';
            PRINT 'SP_SET_PARA_VALUE(2,''PRELOAD_SCAN_NUM'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''PRELOAD_EXTENT_NUM'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''TASK_THREADS'','||TASK_THREADS||');';
        END IF;
        --修改内存检测参数为1
        PRINT 'SP_SET_PARA_VALUE(2,''MEMORY_MAGIC_CHECK'',       1);';
        --修改HASH相关参数
        PRINT 'SP_SET_PARA_VALUE(1,''HJ_BUF_GLOBAL_SIZE'',  '||HJ_BUF_GLOBAL_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(1,''HJ_BUF_SIZE'',        '||HJ_BUF_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(1,''HAGR_BUF_GLOBAL_SIZE'','||HAGR_BUF_GLOBAL_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(1,''HAGR_BUF_SIZE'',     '||HAGR_BUF_SIZE||');';
        --修改排序相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''SORT_FLAG'','||SORT_FLAG||');';
        PRINT 'SP_SET_PARA_VALUE(2,''SORT_BLK_SIZE'','||SORT_BLK_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''SORT_BUF_SIZE'',       '||SORT_BUF_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''SORT_BUF_GLOBAL_SIZE'',       '||SORT_BUF_GLOBAL_SIZE||');';
        --修改其他内存参数
        PRINT 'SP_SET_PARA_VALUE(2,''RLOG_POOL_SIZE'',      '||RLOG_POOL_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''CACHE_POOL_SIZE'',     '||CACHE_POOL_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''DICT_BUF_SIZE'',       '||DICT_BUF_SIZE||');';
        PRINT 'SP_SET_PARA_VALUE(2,''VM_POOL_TARGET'',       16384);';
        PRINT 'SP_SET_PARA_VALUE(2,''SESS_POOL_TARGET'',       16384);';
        --修改实例相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''USE_PLN_POOL'',        1);';
        PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_MONITOR'',      1);';
        PRINT 'SP_SET_PARA_VALUE(2,''SVR_LOG'',             0);';
        PRINT 'SP_SET_PARA_VALUE(2,''TEMP_SIZE'',           1024);';
        PRINT 'SP_SET_PARA_VALUE(2,''TEMP_SPACE_LIMIT'',    102400);';
        PRINT 'SP_SET_PARA_VALUE(2,''MAX_SESSIONS'',        1500);';
        PRINT 'SP_SET_PARA_VALUE(2,''MAX_SESSION_STATEMENT'', 20000);';
        --性能要求高且大字段较少的业务场景建议设置为1，大字段多的场景设置为0
        if pk_cluster_mode = 1 then
            PRINT 'SP_SET_PARA_VALUE(2,''PK_WITH_CLUSTER'',		1);';
        else
            PRINT 'SP_SET_PARA_VALUE(2,''PK_WITH_CLUSTER'',		0);';
        end if;
        PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_ENCRYPT'',0);';
        --修改优化器相关参数
        PRINT 'SP_SET_PARA_VALUE(2,''OLAP_FLAG'',2);';
        PRINT 'SP_SET_PARA_VALUE(2,''VIEW_PULLUP_FLAG'',1);';
        PRINT 'SP_SET_PARA_VALUE(2,''OPTIMIZER_MODE'',1);';
        PRINT 'SP_SET_PARA_VALUE(2,''ADAPTIVE_NPLN_FLAG'',0);';
        --3.163 之前的版本OPTIMIZER_OR_NBEXP不能包含16
        IF VER1>0 AND VER1<=3 AND VER2<163 THEN
            PRINT 'SP_SET_PARA_VALUE(2,''OPTIMIZER_OR_NBEXP'',0);';
        END IF;
        --3.153 之前的版本GROUP_OPT_FLAG不能包含8
        IF VER1>0 AND VER1<=3 AND VER2<153 THEN
            IF EXISTS (SELECT *
                   FROM V\$DM_INI
                  WHERE PARA_NAME = 'GROUP_OPT_FLAG'
                    and DEFAULT_VALUE=60) THEN
                PRINT 'SP_SET_PARA_VALUE(2,''GROUP_OPT_FLAG'',52);';
            END IF;
        END IF;
        --开启并行PURGE
        PRINT 'SP_SET_PARA_VALUE(2,''PARALLEL_PURGE_FLAG'',1);';
        --开启手动并行
        PRINT 'SP_SET_PARA_VALUE(2,''PARALLEL_POLICY'',2);';
        PRINT 'SP_SET_PARA_DOUBLE_VALUE(2,''UNDO_RETENTION'','||UNDO_RETENTION||');';
        --UNDO_RETENTION如果放大，可以适当调大UNDO_EXTENT_NUM。负载高的时候，减少文件系统的申请/释放操作。
        PRINT 'SP_SET_PARA_VALUE(2,''UNDO_EXTENT_NUM'',16);';
        --开启INJECT HINT功能
        PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_INJECT_HINT'',1);';
        PRINT 'SP_SET_PARA_VALUE(2,''BTR_SPLIT_MODE'',1);';
        PRINT 'SP_SET_PARA_VALUE(2,''FAST_LOGIN'',1);';
        --关闭参数监控
        PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_MONITOR_BP'',0);';
        --SLCT_OPT_FLAG参数设置为0
        IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='SLCT_OPT_FLAG') THEN
            PRINT 'SP_SET_PARA_VALUE(1,''SLCT_OPT_FLAG'',0);';
        END IF;
        IF is_dsc= 1 THEN
            PRINT 'SP_SET_PARA_VALUE(2,''ENABLE_FREQROOTS'',0);';
            --DSC关闭数据页预加载参数
            PRINT 'SP_SET_PARA_VALUE(2,''MULTI_PAGE_GET_NUM'',1);';
            PRINT 'SP_SET_PARA_VALUE(2,''PRELOAD_SCAN_NUM'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''PRELOAD_EXTENT_NUM'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''DSC_N_POOLS'',' ||MEMORY_N_POOLS ||');';
            IF EXISTS (SELECT * FROM V\$DM_INI WHERE PARA_NAME='DSC_GBS_REVOKE_OPT') THEN
                PRINT 'SP_SET_PARA_VALUE(2,''DSC_GBS_REVOKE_OPT'',0);';
            END IF;
            PRINT 'SP_SET_PARA_VALUE(2,''DSC_HALT_SYNC'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''DSC_N_CTLS'',50000);';
            PRINT 'SP_SET_PARA_VALUE(2,''DSC_ENABLE_MONITOR'',0);';
            PRINT 'SP_SET_PARA_VALUE(2,''TRX_DICT_LOCK_NUM'',5);';
            PRINT 'SP_SET_PARA_VALUE(2,''DIRECT_IO'',1);';
        END IF;
    END IF;
    select MEMORY_TARGET+BUFFER+RECYCLE+HJ_BUF_GLOBAL_SIZE+HAGR_BUF_GLOBAL_SIZE+CACHE_POOL_SIZE +DICT_BUF_SIZE+SORT_BUF_GLOBAL_SIZE+RLOG_POOL_SIZE;
exception
when others then
    raise_application_error (-20001,substr( ' 执行失败, '||SQLCODE||' '||SQLERRM||' '||dbms_utility.format_error_backtrace , 1, 400));
    <<return_2000>> null;
end;
/
exit;
EOF
  chown dmdba:dinstall "${conf_script}"
  ls -lh "${conf_script}"
  # 创建数据库优化结果查询脚本
  echo
  color_printf blue "创建数据库优化结果查询脚本:"
  echo
  cat <<-EOF >"${query_script}"
	set echo off
set timing off
set lineshow off
set feedback off
set pagesize 1000
select PARA_NAME name,
       PARA_VALUE para_value,
       FILE_VALUE file_value
  from v\$DM_INI
 WHERE PARA_NAME IN ('WORKER_THREADS',
                     'TASK_THREADS',
                     'IO_THR_GROUPS',
                     'MAX_OS_MEMORY',
                     'MEMORY_POOL',
                     'MEMORY_N_POOLS',
                     'MEMORY_TARGET',
                     'BUFFER',
                     'MAX_BUFFER',
                     'BUFFER_POOLS',
                     'RECYCLE',
                     'RECYCLE_POOLS',
                     'FAST_POOL_PAGES',
                     'FAST_ROLL_PAGES',
                     'MEMORY_MAGIC_CHECK',
                     'HJ_BUF_GLOBAL_SIZE',
                     'HJ_BUF_SIZE',
                     'HAGR_BUF_GLOBAL_SIZE',
                     'HAGR_BUF_SIZE',
                     'SORT_FLAG',
                     'SORT_BLK_SIZE',
                     'SORT_BUF_SIZE',
                     'SORT_BUF_GLOBAL_SIZE',
                     'RLOG_POOL_SIZE',
                     'CACHE_POOL_SIZE',
                     'DICT_BUF_SIZE',
                     'VM_POOL_TARGET',
                     'SESS_POOL_TARGET',
                     'USE_PLN_POOL',
                     'ENABLE_MONITOR',
                     'SVR_LOG',
                     'TEMP_SIZE',
                     'TEMP_SPACE_LIMIT',
                     'MAX_SESSIONS',
                     'MAX_SESSION_STATEMENT',
                     'PK_WITH_CLUSTER',
                     'ENABLE_ENCRYPT',
                     'OLAP_FLAG',
                     'VIEW_PULLUP_FLAG',
                     'OPTIMIZER_MODE',
                     'ADAPTIVE_NPLN_FLAG',
                     'PARALLEL_PURGE_FLAG',
                     'PARALLEL_POLICY',
                     'UNDO_EXTENT_NUM',
                     'ENABLE_INJECT_HINT',
                     'BTR_SPLIT_MODE',
                     'FAST_LOGIN',
                     'ENABLE_MONITOR_BP',
                     'MULTI_PAGE_GET_NUM',
                     'PRELOAD_SCAN_NUM',
                     'PRELOAD_EXTENT_NUM',
                     'DSC_N_POOLS',
                     'DSC_GBS_REVOKE_OPT',
                     'DSC_HALT_SYNC',
                     'DSC_N_CTLS',
                     'DSC_ENABLE_MONITOR',
                     'TRX_DICT_LOCK_NUM',
                     'ENABLE_FREQROOTS',
                     'DIRECT_IO')
union all
select 'MAL_CHECK_INTERVAL' ,
       'placeholder',
       CASE WHEN COUNT(MAL_CHECK_INTERVAL)=0 THEN 'NULL' ELSE (SELECT MAL_CHECK_INTERVAL FROM V\$MAL_SYS LIMIT 1) END
  FROM V\$MAL_SYS
union all
select 'MAL_CONN_FAIL_INTERVAL' ,
       'placeholder',
       CASE WHEN COUNT(MAL_CONN_FAIL_INTERVAL)=0 THEN 'NULL' ELSE (SELECT MAL_CONN_FAIL_INTERVAL FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
select 'MAL_BUF_SIZE' ,
       'placeholder',
       CASE WHEN COUNT(MAL_BUF_SIZE)=0 THEN 'NULL' ELSE (SELECT MAL_BUF_SIZE FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
select 'MAL_SYS_BUF_SIZE' ,
       'placeholder',
       CASE WHEN COUNT(MAL_SYS_BUF_SIZE)=0 THEN 'NULL' ELSE (SELECT MAL_SYS_BUF_SIZE FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
select 'MAL_COMPRESS_LEVEL' ,
       'placeholder',
       CASE WHEN COUNT(MAL_COMPRESS_LEVEL)=0 THEN 'NULL' ELSE (SELECT MAL_COMPRESS_LEVEL FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
select 'MAL_TEMP_PATH' ,
       'placeholder',
       CASE WHEN COUNT(MAL_TEMP_PATH)=0 THEN 'NULL' ELSE (SELECT MAL_TEMP_PATH||' ' FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
select 'MAL_VPOOL_SIZE' ,
       'placeholder',
       CASE WHEN COUNT(MAL_VPOOL_SIZE)=0 THEN 'NULL' ELSE (SELECT MAL_VPOOL_SIZE FROM V\$MAL_SYS LIMIT 1) END
  from V\$MAL_SYS
union all
  select 'MAL_INST_NAME' ,
         'placeholder',
         listagg(to_char(MAL_INST_NAME ),',') within GROUP (order by MAL_INST_NAME)
    from V\$dm_MAL_ini
union all
  select 'MAL_HOST' ,
         'placeholder',
         listagg(to_char(MAL_HOST ),',') within GROUP (order by MAL_HOST)
    from V\$dm_MAL_ini
union all
  select 'MAL_PORT' ,
         'placeholder',
         listagg(to_char(MAL_PORT ),',') within GROUP (order by MAL_PORT)
    from V\$dm_MAL_ini
union all
  select 'MAL_INST_HOST' ,
         'placeholder',
         listagg(to_char(MAL_INST_HOST ),',') within GROUP (order by MAL_INST_HOST)
    from V\$dm_MAL_ini
union all
  select 'MAL_INST_PORT' ,
         'placeholder',
         listagg(to_char(MAL_INST_PORT ),',') within GROUP (order by MAL_INST_PORT)
    from V\$dm_MAL_ini
union all
  select 'MAL_DW_PORT' ,
         'placeholder',
         listagg(to_char(MAL_DW_PORT ),',') within GROUP (order by MAL_DW_PORT)
    from V\$dm_MAL_ini
UNION ALL
select 'ARCH_DEST',
       'placeholder',
       CASE WHEN COUNT(ARCH_DEST)=0 THEN 'NULL' ELSE (SELECT ARCH_DEST FROM V\$dm_arch_ini LIMIT 1) END
  from V\$dm_arch_ini;
exit;
EOF
  chown dmdba:dinstall "${query_script}"
  ls -lh "${query_script}"
  echo
  color_printf blue "创建数据库搜集统计信息脚本:"
  echo
  cat <<-EOF >"${conf_statistics}"
set echo off
set timing off
set feedback off
set lineshow off
--call SP_DROP_JOB('statistics');
call SP_CREATE_JOB('statistics',1,0,'',0,0,'',0,'每周六凌晨2点开始收集所有列统计信息');
call SP_JOB_CONFIG_START('statistics');
call SP_ADD_JOB_STEP('statistics', 'statistics1', 0, 'begin
    for rs in (select ''sf_set_SESSION_para_value(''''HAGR_HASH_SIZE'''',
                      (select cast( case when max(table_rowcount(owner,table_name))<=(select max_value from v\$dm_ini where para_Name=''''HAGR_HASH_SIZE'''') and max(table_rowcount(owner,table_name))>=( select min_value from v\$dm_ini where para_Name=''''HAGR_HASH_SIZE'''') then max(table_rowcount(owner,table_name))
                                         when max(table_rowcount(owner,table_name))<( select min_value from v\$dm_ini where para_Name=''''HAGR_HASH_SIZE'''') then (select min_value from v\$dm_ini where para_Name=''''HAGR_HASH_SIZE'''')
                      			         else (select max_value from v\$dm_ini where para_Name=''''HAGR_HASH_SIZE'''')
                      			    end as bigint)
                      from dba_tables
                      where owner=''''''||NAME||''''''));'' sql1,
                      ''DBMS_STATS.GATHER_SCHEMA_STATS(''''''||NAME||'''''',100,TRUE,''''FOR ALL COLUMNS SIZE AUTO'''');'' sql2
              from SYS.SYSOBJECTS
              where TYPE\$=''SCH'')
    loop
        execute immediate rs.sql1;
        execute immediate rs.sql2;
    end loop;
end;', 0, 0, 0, 0, NULL, 0);
call SP_ADD_JOB_SCHEDULE('statistics', 'statistics1', 1, 2, 1, 64, 0, '02:00:00', NULL, '2021-06-09 22:54:37', NULL, '');
call SP_JOB_CONFIG_COMMIT('statistics');
SELECT NAME,
       describe
  FROM sysjob.sysjobs
  where name = 'statistics';
exit;
EOF
  chown dmdba:dinstall "${conf_statistics}"
  ls -lh "${conf_statistics}"
}
#==============================================================#
#                    优化数据库                                 #
#==============================================================#
function optimize_dmdb() {
  # 配置glogin "配置 glogin.sql"
  conf_glogin
  # 配置数据库归档
  if [[ $dm_install_mode = "single" && $enable_arch = "Y" ]]; then
    # 启用归档日志
    log_print "配置数据库归档" ""
    execute_script "conf_arch.sql"
  fi
  # 创建数据库备份
  backup_if_enabled() {
    local ip=${1}
    if [[ $enable_arch = "Y" ]]; then
      log_print "在 ${ip} 创建备份" ""
      if [[ $backup_mode -eq 1 ]]; then
        execute_script "conf_fullbackup.sql"
      elif [[ $backup_mode -eq 2 ]]; then
        execute_script "conf_incrbackup.sql"
      fi
    fi
  }
  if [[ $dm_install_mode = "dsc" || $dm_install_mode = "dscm" ]]; then
    if [[ $enable_arch = "Y" ]]; then
      lastIndex=$((${#wc_hostip_arr[@]} - 1))
      backup_if_enabled "${wc_hostip_arr[$lastIndex]}"
    fi
  elif [[ $dm_install_mode = "datawatch" && $node_num -eq 1 ]]; then
    backup_if_enabled "${wc_hostip_arr[0]}"
  else
    backup_if_enabled "127.0.0.1"
  fi
  # 创建搜集统计信息作业
  if [[ $node_num -eq 1 ]]; then
    log_print "配置搜集统计信息作业" ""
    if [[ $enable_arch = "N" ]]; then
      execute_sql "SP_INIT_JOB_SYS(1);"
    fi
    execute_script "conf_statistics.sql"
  fi
  # 优化数据库基础参数
  log_print "优化数据库基础参数" ""
  execute_script "conf_dmini.sql"
  if [[ $compatible_mode -ne 0 ]]; then
    log_print "打开兼容 $compatible_mode 参数" ""
    execute_sql "sp_set_para_value(2,'COMPATIBLE_MODE',${compatible_mode});"
  fi
  # 开启操作系统认证
  log_print "开启操作系统认证" ""
  execute_sql "sp_set_para_value(2,'ENABLE_LOCAL_OSAUTH',1);"
  # 开启SQLLOG日志
  log_print "开启SQLLOG日志" ""
  execute_sql "sp_set_para_value(2,'SVR_LOG',1);"
  log_print "重启数据库，优化参数生效" ""
  if [[ $dm_install_mode = "dsc" || $dm_install_mode = "dscm" ]]; then
    color_printf red "关闭数据库"
    echo
    su - dmdba -c "$env_dm_home/bin/dmcssm $data_dir/$dsc_db_name/dmcssm.ini" <<-DMCSSM
ep stop GRP_$dsc_db_name
exit
DMCSSM
    while true; do
      sleep 5s
      PID=$(pgrep -f "$data_dir"/"$dsc_db_name"/"$dsc_ep_name"0_config/dm.ini)
      [ ! "$PID" ] && break
    done
    color_printf red "启动数据库"
    echo
    su - dmdba -c "$env_dm_home/bin/dmcssm $data_dir/$dsc_db_name/dmcssm.ini" <<-DMCSSM
ep startup GRP_$dsc_db_name
exit
DMCSSM
    config_file="$data_dir/$dsc_db_name/$dsc_ep_name"0_config/dm.ini
    timeout=120 # 等待2分钟
    start_time=$(date +%s)
    while true; do
      sleep 5s
      elapsed_time=$(($(date +%s) - start_time))
      if [ $elapsed_time -ge $timeout ]; then
        echo "超时：无法建立数据库连接"
        exit 1
      fi
      PID=$(pgrep -f "$config_file")
      if [ -n "$PID" ]; then
        connResult=$(execute_sql "select 1" | tail -1)
        if [ "$connResult" = "1" ]; then
          break
        fi
      fi
    done
  else
    su - dmdba -c "$DMS_RESTART_CMD"
  fi
  if [[ $dm_install_mode = "single" || $dm_install_mode = "dsc" || $dm_install_mode = "dscm" ]]; then
    # 执行备份脚本，备份数据库
    log_print "测试作业备份数据库" ""
    execute_script "check_backup.sql"
    # 创建dmdba数据库用户
    log_print "创建DMDBA用户，密码：$sysdba_pwd" ""
    execute_script "create_user.sql"
    log_print "查询数据库优化结果：" ""
    su - dmdba -c "disql -S SYSDBA/'\"$sysdba_pwd\"':$port_num \\\`${scripts_dir}/query_dmini.sql"
  fi
}
#==============================================================#
#                    重启服务器                                  #
#==============================================================#
restart_hosts() {
  # 定义数据库服务关闭函数
  restart_host() {
    local host="$1"
    local stop_cmd="$2"
    color_printf red "正在关闭 ${host} 节点数据库服务......"
    echo
    exec_ssh_cmd "$host" "su - dmdba -c \"${stop_cmd}\""
    echo
  }
  # 重启服务器
  restart_hosts_with_reboot() {
    local stop_cmd="$1"
    for ((i = 0; i < ${#wc_pubip_arr[@]} + ${#as_pubip_arr[@]}; i++)); do
      restart_host "${wc_hostip_arr[i]}" "$stop_cmd"
      if ((i > 0)); then
        exec_ssh_cmd "${wc_hostip_arr[i]}" "shutdown -r now"
        echo
      fi
    done
    shutdown -r now
    echo
  }
  # 选择要重启的模式
  case $dm_install_mode in
  "single")
    echo
    read -rep "$(echo -e "\033[1;34m恭喜！单机安装成功，现在是否重启主机：[Y/N] \E[0m")" isreboot
    echo
    if [[ $isreboot == [Yy] ]]; then
      shutdown -r now
    else
      exit 1
    fi
    ;;
  "datawatch")
    echo
    read -rep "$(echo -e "\033[1;34m恭喜！数据守护集群安装成功，现在是否重启主机：[Y/N] \E[0m")" isreboot
    echo
    if [[ $isreboot == [Yy] ]]; then
      # 如果监视器存在，关闭监视器
      if [[ -n "${dw_monitor_ip}" ]]; then
        restart_host "${dw_monitor_ip}" "$DMM_STOP_CMD"
        exec_ssh_cmd "${dw_monitor_ip}" "shutdown -r now"
        echo
      fi
      # 重启数据库实例节点
      restart_hosts_with_reboot "$DMW_STOP_CMD && $DMS_STOP_CMD"
    else
      exit 1
    fi
    ;;
  "dsc" | "dscm")
    echo
    read -rep "$(echo -e "\033[1;34m恭喜！DSC集群安装成功，现在是否重启主机：[Y/N] \E[0m")" isreboot
    echo
    if [[ $isreboot == [Yy] ]]; then
      color_printf red "正在重启 ......"
      echo
      color_printf red "正在关闭 DB  实例......"
      echo
      su - dmdba -c "$env_dm_home/bin/dmcssm $data_dir/$dsc_db_name/dmcssm.ini" <<-EOF
ep stop GRP_$dsc_db_name
exit
EOF
      echo
      color_printf red "正在关闭 ASM 实例......"
      echo
      su - dmdba -c "$env_dm_home/bin/dmcssm $data_dir/$dsc_db_name/dmcssm.ini" <<-'EOF'
ep stop GRP_ASM
exit
EOF
      restart_hosts_with_reboot "$DMC_STOP_CMD"
    else
      exit 1
    fi
    ;;
  esac
}
#==============================================================#
#                    程序入口                                   #
#==============================================================#
function main() {
  # 打印提示
  script_print
  # 将参数写入文件
  echo -e "\nsh ${software_dir}/$(basename "$0") $*" >"${dminstalllog}"
  # 检查脚本名称是否为 DMShellInstall
  if [[ "$(basename "$0")" != "DMShellInstall" ]]; then
    echo
    color_printf red "本脚本不允许修改脚本名称，请修改回：DMShellInstall，已退出!"
    echo
    exit 1
  fi
  # 判断当前执行脚本用户是否为 root 用户
  if [[ "$(id -u)" -ne 0 ]]; then
    echo
    color_printf red "本脚本需要使用 root 用户执行，已退出!"
    echo
    exit 1
  fi
  # 检验脚本传入参数
  check_para "$@"
  # 检查当前目录是否是root根目录以及的/dm目录，否则退出
  if [[ ${software_dir} = "/root" || ${software_dir} = "$env_dm_home" ]]; then
    echo
    color_printf red "达梦软件包以及脚本位置不能是/root或者$env_dm_home!"
    echo
    exit 1
  fi
  # 判断当前目录是否存在 DMInstall.bin 文件，如果不存在且 -di 参数为空，则报错退出
  if ! [[ -f ${software_dir}/${dm_iso_name} ]] && ! check_file "${software_dir}"/DMInstall.bin; then
    echo
    color_printf red "当前目录不存在达梦安装程序 DMInstall.bin，并且没有指定参数 -di，无法继续安装！"
    echo
    exit 1
  fi
  # 校验服务器是否存在dirname,mount,tar工具
  command_check
  # 所有参数校验完成，根据ARG_CHECK_FLAG标志，判断是否推出本次安装
  if [ $ARG_CHECK_FLAG -eq 0 ]; then
    echo
    color_printf red " 参数校验不通过！请检查屏幕上输出的参数异常信息或者查看${dminstalllog} 文件获取异常参数信息，处理后再次校验！"
    echo
    exit 1
  fi
  # 节点一选择数据库安装模式
  select_install_mode
  # 获取操作系统版本
  define_os_flag
  # 去除显示登录提示和密码复杂度
  rm_passwd_complexity
  # 校验服务器IP和磁盘
  [[ $dm_install_mode == "datawatch" || $dm_install_mode == "dsc" || $dm_install_mode = "dscm" ]] && validate_ip_disk
  # 开始安装数据库
  begin_install_dmdb "$@"
  # 主程序入口
  if [[ $dm_install_mode == "datawatch" || $dm_install_mode == "dsc" || $dm_install_mode = "dscm" ]]; then
    if [[ $node_num -eq 1 ]]; then
      log_print "配置节点: ${wc_hostip_arr[0]}" ""
      color_printf yellow "节点 ${wc_hostip_arr[0]} 开始配置:"
    fi
  fi
  # 打印系统信息
  print_sysinfo
  # 优化数据库
  common_operations() {
    conf_arch_bak_user
    optimize_dmini
  }
  # 初始化数据库
  prepare_db_without_dsc() {
    init_dmdb
    install_dmdb_svc
    start_dmdb
    common_operations
    dm_sqllog
    [[ $optimize_db == "Y" ]] && optimize_dmdb
  }
  # 安装数据库守护
  datawatch_operations() {
    if [[ $node_num -eq 1 ]]; then
      dw_pri_backup
      other_node_shell
    elif [[ $node_num -ne -1 ]]; then
      dw_std_restore
    fi
    # 每个节点创建数据库参数
    if [[ $node_num -ne -1 ]]; then
      dw_dmini
      dw_dmmal
      dw_dmarch
      dw_dmwatcher
      dw_timer
    fi
    # 创建监视器
    dw_monitor
    # 修改数据库模式
    if [[ $node_num -ne -1 ]]; then
      dw_conf_para
    fi
  }
  # 安装DSC集群
  dsc_operations() {
    dsc_conf_disk_arr
    dsc_dmdcr_cfg
    dsc_dmasvmal
    dsc_dmdcr
    dsc_dmcssm
    dsc_dmsvc
    dsc_dmservice
    common_operations
    if [[ $node_num -eq 1 ]]; then
      other_node_shell
      dsc_dmasmcmd
      dsc_start_ca
      dsc_dmasmtool
      init_dsc_db
      dm_sqllog
      dsc_start
    fi
  }
  # 重启数据库
  prepare_db() {
    # 判断安装数据库类型
    case $dm_install_mode in
    "datawatch")
      [[ $node_num -ne -1 ]] && prepare_db_without_dsc
      datawatch_operations
      ;;
    "dsc")
      dsc_operations
      ;;
    "dscm")
      dsc_operations
      ;;
    *)
      prepare_db_without_dsc
      ;;
    esac
    echo
    if [[ $node_num -eq 1 ]]; then
      restart_hosts
    fi
  }
  # 开始安装数据库
  if [[ $only_optimize_db == "N" ]]; then
    conf_firewall
    conf_selinux
    conf_swap
    conf_grub
    conf_sysctl
    conf_limits
    conf_system_nproc
    conf_ipc
    # 配置NTP服务
    [[ -n $timeserver_ip ]] && conf_timesync
    conf_drop_cache
    conf_profile
    # 配置主机名
    [[ -n $hostname ]] && conf_hostname
    create_user
    create_dir
    conf_user_profile
    # 创建数据库镜像挂载目录
    generate_random_string
    # 挂载ISO镜像
    [[ -n "${dm_iso_name}" ]] && mount_iso
    # 仅配置OS环境，是退出脚本执行
    # shellcheck disable=SC2015
    [[ $only_conf_os == "N" ]] && {
      # 是否仅初始化数据库
      install_dmdb
      #定义数据库服务关闭函数
      db_service_cmd
      if [[ $only_init_db == "N" ]]; then
        # 是否仅安装数据库软件
        [[ $install_until_db == "N" || $node_num -eq -1 ]] && prepare_db || restart_hosts
      else
        prepare_db
      fi
    } || restart_hosts
  else
    db_service_cmd
    common_operations
    dm_sqllog
    [[ $optimize_db == "Y" ]] && optimize_dmdb
    restart_hosts
  fi
}
main "$@" | tee -a "$dminstalllog"
